mirror of
https://github.com/scylladb/scylladb.git
synced 2026-04-28 12:17:02 +00:00
Quoth @avikivity: "switch is not a function, and we celebrate that by putting a space after it like other control-flow keywords." https://github.com/scylladb/scylla/pull/7052#discussion_r471932710 Tests: unit (dev) Signed-off-by: Dejan Mircevski <dejan@scylladb.com>
696 lines
31 KiB
C++
696 lines
31 KiB
C++
/*
|
|
* Copyright 2019 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 Affero General Public License
|
|
* along with Scylla. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include "expressions.hh"
|
|
#include "serialization.hh"
|
|
#include "base64.hh"
|
|
#include "conditions.hh"
|
|
#include "alternator/expressionsLexer.hpp"
|
|
#include "alternator/expressionsParser.hpp"
|
|
#include "utils/overloaded_functor.hh"
|
|
#include "error.hh"
|
|
|
|
#include "seastarx.hh"
|
|
|
|
#include <seastar/core/print.hh>
|
|
#include <seastar/util/log.hh>
|
|
|
|
#include <boost/algorithm/cxx11/any_of.hpp>
|
|
#include <boost/algorithm/cxx11/all_of.hpp>
|
|
|
|
#include <functional>
|
|
#include <unordered_map>
|
|
|
|
namespace alternator {
|
|
|
|
template <typename Func, typename Result = std::result_of_t<Func(expressionsParser&)>>
|
|
Result do_with_parser(std::string input, Func&& f) {
|
|
expressionsLexer::InputStreamType input_stream{
|
|
reinterpret_cast<const ANTLR_UINT8*>(input.data()),
|
|
ANTLR_ENC_UTF8,
|
|
static_cast<ANTLR_UINT32>(input.size()),
|
|
nullptr };
|
|
expressionsLexer lexer(&input_stream);
|
|
expressionsParser::TokenStreamType tstream(ANTLR_SIZE_HINT, lexer.get_tokSource());
|
|
expressionsParser parser(&tstream);
|
|
|
|
auto result = f(parser);
|
|
return result;
|
|
}
|
|
|
|
parsed::update_expression
|
|
parse_update_expression(std::string query) {
|
|
try {
|
|
return do_with_parser(query, std::mem_fn(&expressionsParser::update_expression));
|
|
} catch (...) {
|
|
throw expressions_syntax_error(format("Failed parsing UpdateExpression '{}': {}", query, std::current_exception()));
|
|
}
|
|
}
|
|
|
|
std::vector<parsed::path>
|
|
parse_projection_expression(std::string query) {
|
|
try {
|
|
return do_with_parser(query, std::mem_fn(&expressionsParser::projection_expression));
|
|
} catch (...) {
|
|
throw expressions_syntax_error(format("Failed parsing ProjectionExpression '{}': {}", query, std::current_exception()));
|
|
}
|
|
}
|
|
|
|
parsed::condition_expression
|
|
parse_condition_expression(std::string query) {
|
|
try {
|
|
return do_with_parser(query, std::mem_fn(&expressionsParser::condition_expression));
|
|
} catch (...) {
|
|
throw expressions_syntax_error(format("Failed parsing ConditionExpression '{}': {}", query, std::current_exception()));
|
|
}
|
|
}
|
|
|
|
namespace parsed {
|
|
|
|
void update_expression::add(update_expression::action a) {
|
|
std::visit(overloaded_functor {
|
|
[&] (action::set&) { seen_set = true; },
|
|
[&] (action::remove&) { seen_remove = true; },
|
|
[&] (action::add&) { seen_add = true; },
|
|
[&] (action::del&) { seen_del = true; }
|
|
}, a._action);
|
|
_actions.push_back(std::move(a));
|
|
}
|
|
|
|
void update_expression::append(update_expression other) {
|
|
if ((seen_set && other.seen_set) ||
|
|
(seen_remove && other.seen_remove) ||
|
|
(seen_add && other.seen_add) ||
|
|
(seen_del && other.seen_del)) {
|
|
throw expressions_syntax_error("Each of SET, REMOVE, ADD, DELETE may only appear once in UpdateExpression");
|
|
}
|
|
std::move(other._actions.begin(), other._actions.end(), std::back_inserter(_actions));
|
|
seen_set |= other.seen_set;
|
|
seen_remove |= other.seen_remove;
|
|
seen_add |= other.seen_add;
|
|
seen_del |= other.seen_del;
|
|
}
|
|
|
|
void condition_expression::append(condition_expression&& a, char op) {
|
|
std::visit(overloaded_functor {
|
|
[&] (condition_list& x) {
|
|
// If 'a' has a single condition, we could, instead of inserting
|
|
// it insert its single condition (possibly negated if a._negated)
|
|
// But considering it we don't evaluate these expressions many
|
|
// times, this optimization is not worth extra code complexity.
|
|
if (!x.conditions.empty() && x.op != op) {
|
|
// Shouldn't happen unless we have a bug in the parser
|
|
throw std::logic_error("condition_expression::append called with mixed operators");
|
|
}
|
|
x.conditions.push_back(std::move(a));
|
|
x.op = op;
|
|
},
|
|
[&] (primitive_condition& x) {
|
|
// Shouldn't happen unless we have a bug in the parser
|
|
throw std::logic_error("condition_expression::append called on primitive_condition");
|
|
}
|
|
}, _expression);
|
|
}
|
|
|
|
} // namespace parsed
|
|
|
|
// The following resolve_*() functions resolve references in parsed
|
|
// expressions of different types. Resolving a parsed expression means
|
|
// replacing:
|
|
// 1. In parsed::path objects, replace references like "#name" with the
|
|
// attribute name from ExpressionAttributeNames,
|
|
// 2. In parsed::constant objects, replace references like ":value" with
|
|
// the value from ExpressionAttributeValues.
|
|
// These function also track which name and value references were used, to
|
|
// allow complaining if some remain unused.
|
|
// Note that the resolve_*() functions modify the expressions in-place,
|
|
// so if we ever intend to cache parsed expression, we need to pass a copy
|
|
// into this function.
|
|
//
|
|
// Doing the "resolving" stage before the evaluation stage has two benefits.
|
|
// First, it allows us to be compatible with DynamoDB in catching unused
|
|
// names and values (see issue #6572). Second, in the FilterExpression case,
|
|
// we need to resolve the expression just once but then use it many times
|
|
// (once for each item to be filtered).
|
|
|
|
static void resolve_path(parsed::path& p,
|
|
const rjson::value* expression_attribute_names,
|
|
std::unordered_set<std::string>& used_attribute_names) {
|
|
const std::string& column_name = p.root();
|
|
if (column_name.size() > 0 && column_name.front() == '#') {
|
|
if (!expression_attribute_names) {
|
|
throw api_error::validation(
|
|
format("ExpressionAttributeNames missing, entry '{}' required by expression", column_name));
|
|
}
|
|
const rjson::value* value = rjson::find(*expression_attribute_names, column_name);
|
|
if (!value || !value->IsString()) {
|
|
throw api_error::validation(
|
|
format("ExpressionAttributeNames missing entry '{}' required by expression", column_name));
|
|
}
|
|
used_attribute_names.emplace(column_name);
|
|
p.set_root(std::string(rjson::to_string_view(*value)));
|
|
}
|
|
}
|
|
|
|
static void resolve_constant(parsed::constant& c,
|
|
const rjson::value* expression_attribute_values,
|
|
std::unordered_set<std::string>& used_attribute_values) {
|
|
std::visit(overloaded_functor {
|
|
[&] (const std::string& valref) {
|
|
if (!expression_attribute_values) {
|
|
throw api_error::validation(
|
|
format("ExpressionAttributeValues missing, entry '{}' required by expression", valref));
|
|
}
|
|
const rjson::value* value = rjson::find(*expression_attribute_values, valref);
|
|
if (!value) {
|
|
throw api_error::validation(
|
|
format("ExpressionAttributeValues missing entry '{}' required by expression", valref));
|
|
}
|
|
if (value->IsNull()) {
|
|
throw api_error::validation(
|
|
format("ExpressionAttributeValues null value for entry '{}' required by expression", valref));
|
|
}
|
|
validate_value(*value, "ExpressionAttributeValues");
|
|
used_attribute_values.emplace(valref);
|
|
c.set(*value);
|
|
},
|
|
[&] (const parsed::constant::literal& lit) {
|
|
// Nothing to do, already resolved
|
|
}
|
|
}, c._value);
|
|
|
|
}
|
|
|
|
void resolve_value(parsed::value& rhs,
|
|
const rjson::value* expression_attribute_names,
|
|
const rjson::value* expression_attribute_values,
|
|
std::unordered_set<std::string>& used_attribute_names,
|
|
std::unordered_set<std::string>& used_attribute_values) {
|
|
std::visit(overloaded_functor {
|
|
[&] (parsed::constant& c) {
|
|
resolve_constant(c, expression_attribute_values, used_attribute_values);
|
|
},
|
|
[&] (parsed::value::function_call& f) {
|
|
for (parsed::value& value : f._parameters) {
|
|
resolve_value(value, expression_attribute_names, expression_attribute_values,
|
|
used_attribute_names, used_attribute_values);
|
|
}
|
|
},
|
|
[&] (parsed::path& p) {
|
|
resolve_path(p, expression_attribute_names, used_attribute_names);
|
|
}
|
|
}, rhs._value);
|
|
}
|
|
|
|
void resolve_set_rhs(parsed::set_rhs& rhs,
|
|
const rjson::value* expression_attribute_names,
|
|
const rjson::value* expression_attribute_values,
|
|
std::unordered_set<std::string>& used_attribute_names,
|
|
std::unordered_set<std::string>& used_attribute_values) {
|
|
resolve_value(rhs._v1, expression_attribute_names, expression_attribute_values,
|
|
used_attribute_names, used_attribute_values);
|
|
if (rhs._op != 'v') {
|
|
resolve_value(rhs._v2, expression_attribute_names, expression_attribute_values,
|
|
used_attribute_names, used_attribute_values);
|
|
}
|
|
}
|
|
|
|
void resolve_update_expression(parsed::update_expression& ue,
|
|
const rjson::value* expression_attribute_names,
|
|
const rjson::value* expression_attribute_values,
|
|
std::unordered_set<std::string>& used_attribute_names,
|
|
std::unordered_set<std::string>& used_attribute_values) {
|
|
for (parsed::update_expression::action& action : ue.actions()) {
|
|
resolve_path(action._path, expression_attribute_names, used_attribute_names);
|
|
std::visit(overloaded_functor {
|
|
[&] (parsed::update_expression::action::set& a) {
|
|
resolve_set_rhs(a._rhs, expression_attribute_names, expression_attribute_values,
|
|
used_attribute_names, used_attribute_values);
|
|
},
|
|
[&] (parsed::update_expression::action::remove& a) {
|
|
// nothing to do
|
|
},
|
|
[&] (parsed::update_expression::action::add& a) {
|
|
resolve_constant(a._valref, expression_attribute_values, used_attribute_values);
|
|
},
|
|
[&] (parsed::update_expression::action::del& a) {
|
|
resolve_constant(a._valref, expression_attribute_values, used_attribute_values);
|
|
}
|
|
}, action._action);
|
|
}
|
|
}
|
|
|
|
static void resolve_primitive_condition(parsed::primitive_condition& pc,
|
|
const rjson::value* expression_attribute_names,
|
|
const rjson::value* expression_attribute_values,
|
|
std::unordered_set<std::string>& used_attribute_names,
|
|
std::unordered_set<std::string>& used_attribute_values) {
|
|
for (parsed::value& value : pc._values) {
|
|
resolve_value(value,
|
|
expression_attribute_names, expression_attribute_values,
|
|
used_attribute_names, used_attribute_values);
|
|
}
|
|
}
|
|
|
|
void resolve_condition_expression(parsed::condition_expression& ce,
|
|
const rjson::value* expression_attribute_names,
|
|
const rjson::value* expression_attribute_values,
|
|
std::unordered_set<std::string>& used_attribute_names,
|
|
std::unordered_set<std::string>& used_attribute_values) {
|
|
std::visit(overloaded_functor {
|
|
[&] (parsed::primitive_condition& cond) {
|
|
resolve_primitive_condition(cond,
|
|
expression_attribute_names, expression_attribute_values,
|
|
used_attribute_names, used_attribute_values);
|
|
},
|
|
[&] (parsed::condition_expression::condition_list& list) {
|
|
for (parsed::condition_expression& cond : list.conditions) {
|
|
resolve_condition_expression(cond,
|
|
expression_attribute_names, expression_attribute_values,
|
|
used_attribute_names, used_attribute_values);
|
|
|
|
}
|
|
}
|
|
}, ce._expression);
|
|
}
|
|
|
|
void resolve_projection_expression(std::vector<parsed::path>& pe,
|
|
const rjson::value* expression_attribute_names,
|
|
std::unordered_set<std::string>& used_attribute_names) {
|
|
for (parsed::path& p : pe) {
|
|
resolve_path(p, expression_attribute_names, used_attribute_names);
|
|
}
|
|
}
|
|
|
|
// condition_expression_on() checks whether a condition_expression places any
|
|
// condition on the given attribute. It can be useful, for example, for
|
|
// checking whether the condition tries to restrict a key column.
|
|
|
|
static bool value_on(const parsed::value& v, std::string_view attribute) {
|
|
return std::visit(overloaded_functor {
|
|
[&] (const parsed::constant& c) {
|
|
return false;
|
|
},
|
|
[&] (const parsed::value::function_call& f) {
|
|
for (const parsed::value& value : f._parameters) {
|
|
if (value_on(value, attribute)) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
},
|
|
[&] (const parsed::path& p) {
|
|
return p.root() == attribute;
|
|
}
|
|
}, v._value);
|
|
}
|
|
|
|
static bool primitive_condition_on(const parsed::primitive_condition& pc, std::string_view attribute) {
|
|
for (const parsed::value& value : pc._values) {
|
|
if (value_on(value, attribute)) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool condition_expression_on(const parsed::condition_expression& ce, std::string_view attribute) {
|
|
return std::visit(overloaded_functor {
|
|
[&] (const parsed::primitive_condition& cond) {
|
|
return primitive_condition_on(cond, attribute);
|
|
},
|
|
[&] (const parsed::condition_expression::condition_list& list) {
|
|
for (const parsed::condition_expression& cond : list.conditions) {
|
|
if (condition_expression_on(cond, attribute)) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
}, ce._expression);
|
|
}
|
|
|
|
// The following calculate_value() functions calculate, or evaluate, a parsed
|
|
// expression. The parsed expression is assumed to have been "resolved", with
|
|
// the matching resolve_* function.
|
|
|
|
// Take two JSON-encoded list values (remember that a list value is
|
|
// {"L": [...the actual list]}) and return the concatenation, again as
|
|
// a list value.
|
|
static rjson::value list_concatenate(const rjson::value& v1, const rjson::value& v2) {
|
|
const rjson::value* list1 = unwrap_list(v1);
|
|
const rjson::value* list2 = unwrap_list(v2);
|
|
if (!list1 || !list2) {
|
|
throw api_error::validation("UpdateExpression: list_append() given a non-list");
|
|
}
|
|
rjson::value cat = rjson::copy(*list1);
|
|
for (const auto& a : list2->GetArray()) {
|
|
rjson::push_back(cat, rjson::copy(a));
|
|
}
|
|
rjson::value ret = rjson::empty_object();
|
|
rjson::set(ret, "L", std::move(cat));
|
|
return ret;
|
|
}
|
|
|
|
// calculate_size() is ConditionExpression's size() function, i.e., it takes
|
|
// a JSON-encoded value and returns its "size" as defined differently for the
|
|
// different types - also as a JSON-encoded number.
|
|
// It return a JSON-encoded "null" value if this value's type has no size
|
|
// defined. Comparisons against this non-numeric value will later fail.
|
|
static rjson::value calculate_size(const rjson::value& v) {
|
|
// NOTE: If v is improperly formatted for our JSON value encoding, it
|
|
// must come from the request itself, not from the database, so it makes
|
|
// sense to throw a ValidationException if we see such a problem.
|
|
if (!v.IsObject() || v.MemberCount() != 1) {
|
|
throw api_error::validation(format("invalid object: {}", v));
|
|
}
|
|
auto it = v.MemberBegin();
|
|
int ret;
|
|
if (it->name == "S") {
|
|
if (!it->value.IsString()) {
|
|
throw api_error::validation(format("invalid string: {}", v));
|
|
}
|
|
ret = it->value.GetStringLength();
|
|
} else if (it->name == "NS" || it->name == "SS" || it->name == "BS" || it->name == "L") {
|
|
if (!it->value.IsArray()) {
|
|
throw api_error::validation(format("invalid set: {}", v));
|
|
}
|
|
ret = it->value.Size();
|
|
} else if (it->name == "M") {
|
|
if (!it->value.IsObject()) {
|
|
throw api_error::validation(format("invalid map: {}", v));
|
|
}
|
|
ret = it->value.MemberCount();
|
|
} else if (it->name == "B") {
|
|
if (!it->value.IsString()) {
|
|
throw api_error::validation(format("invalid byte string: {}", v));
|
|
}
|
|
ret = base64_decoded_len(rjson::to_string_view(it->value));
|
|
} else {
|
|
rjson::value json_ret = rjson::empty_object();
|
|
rjson::set(json_ret, "null", rjson::value(true));
|
|
return json_ret;
|
|
}
|
|
rjson::value json_ret = rjson::empty_object();
|
|
rjson::set(json_ret, "N", rjson::from_string(std::to_string(ret)));
|
|
return json_ret;
|
|
}
|
|
|
|
static const rjson::value& calculate_value(const parsed::constant& c) {
|
|
return std::visit(overloaded_functor {
|
|
[&] (const parsed::constant::literal& v) -> const rjson::value& {
|
|
return *v;
|
|
},
|
|
[&] (const std::string& valref) -> const rjson::value& {
|
|
// Shouldn't happen, we should have called resolve_value() earlier
|
|
// and replaced the value reference by the literal constant.
|
|
throw std::logic_error("calculate_value() called before resolve_value()");
|
|
}
|
|
}, c._value);
|
|
}
|
|
|
|
static rjson::value to_bool_json(bool b) {
|
|
rjson::value json_ret = rjson::empty_object();
|
|
rjson::set(json_ret, "BOOL", rjson::value(b));
|
|
return json_ret;
|
|
}
|
|
|
|
static bool known_type(std::string_view type) {
|
|
static thread_local const std::unordered_set<std::string_view> types = {
|
|
"N", "S", "B", "NS", "SS", "BS", "L", "M", "NULL", "BOOL"
|
|
};
|
|
return types.contains(type);
|
|
}
|
|
|
|
using function_handler_type = rjson::value(calculate_value_caller, const rjson::value*, const parsed::value::function_call&);
|
|
static const
|
|
std::unordered_map<std::string_view, function_handler_type*> function_handlers {
|
|
{"list_append", [] (calculate_value_caller caller, const rjson::value* previous_item, const parsed::value::function_call& f) {
|
|
if (caller != calculate_value_caller::UpdateExpression) {
|
|
throw api_error::validation(
|
|
format("{}: list_append() not allowed here", caller));
|
|
}
|
|
if (f._parameters.size() != 2) {
|
|
throw api_error::validation(
|
|
format("{}: list_append() accepts 2 parameters, got {}", caller, f._parameters.size()));
|
|
}
|
|
rjson::value v1 = calculate_value(f._parameters[0], caller, previous_item);
|
|
rjson::value v2 = calculate_value(f._parameters[1], caller, previous_item);
|
|
return list_concatenate(v1, v2);
|
|
}
|
|
},
|
|
{"if_not_exists", [] (calculate_value_caller caller, const rjson::value* previous_item, const parsed::value::function_call& f) {
|
|
if (caller != calculate_value_caller::UpdateExpression) {
|
|
throw api_error::validation(
|
|
format("{}: if_not_exists() not allowed here", caller));
|
|
}
|
|
if (f._parameters.size() != 2) {
|
|
throw api_error::validation(
|
|
format("{}: if_not_exists() accepts 2 parameters, got {}", caller, f._parameters.size()));
|
|
}
|
|
if (!std::holds_alternative<parsed::path>(f._parameters[0]._value)) {
|
|
throw api_error::validation(
|
|
format("{}: if_not_exists() must include path as its first argument", caller));
|
|
}
|
|
rjson::value v1 = calculate_value(f._parameters[0], caller, previous_item);
|
|
rjson::value v2 = calculate_value(f._parameters[1], caller, previous_item);
|
|
return v1.IsNull() ? std::move(v2) : std::move(v1);
|
|
}
|
|
},
|
|
{"size", [] (calculate_value_caller caller, const rjson::value* previous_item, const parsed::value::function_call& f) {
|
|
if (caller != calculate_value_caller::ConditionExpression) {
|
|
throw api_error::validation(
|
|
format("{}: size() not allowed here", caller));
|
|
}
|
|
if (f._parameters.size() != 1) {
|
|
throw api_error::validation(
|
|
format("{}: size() accepts 1 parameter, got {}", caller, f._parameters.size()));
|
|
}
|
|
rjson::value v = calculate_value(f._parameters[0], caller, previous_item);
|
|
return calculate_size(v);
|
|
}
|
|
},
|
|
{"attribute_exists", [] (calculate_value_caller caller, const rjson::value* previous_item, const parsed::value::function_call& f) {
|
|
if (caller != calculate_value_caller::ConditionExpressionAlone) {
|
|
throw api_error::validation(
|
|
format("{}: attribute_exists() not allowed here", caller));
|
|
}
|
|
if (f._parameters.size() != 1) {
|
|
throw api_error::validation(
|
|
format("{}: attribute_exists() accepts 1 parameter, got {}", caller, f._parameters.size()));
|
|
}
|
|
if (!std::holds_alternative<parsed::path>(f._parameters[0]._value)) {
|
|
throw api_error::validation(
|
|
format("{}: attribute_exists()'s parameter must be a path", caller));
|
|
}
|
|
rjson::value v = calculate_value(f._parameters[0], caller, previous_item);
|
|
return to_bool_json(!v.IsNull());
|
|
}
|
|
},
|
|
{"attribute_not_exists", [] (calculate_value_caller caller, const rjson::value* previous_item, const parsed::value::function_call& f) {
|
|
if (caller != calculate_value_caller::ConditionExpressionAlone) {
|
|
throw api_error::validation(
|
|
format("{}: attribute_not_exists() not allowed here", caller));
|
|
}
|
|
if (f._parameters.size() != 1) {
|
|
throw api_error::validation(
|
|
format("{}: attribute_not_exists() accepts 1 parameter, got {}", caller, f._parameters.size()));
|
|
}
|
|
if (!std::holds_alternative<parsed::path>(f._parameters[0]._value)) {
|
|
throw api_error::validation(
|
|
format("{}: attribute_not_exists()'s parameter must be a path", caller));
|
|
}
|
|
rjson::value v = calculate_value(f._parameters[0], caller, previous_item);
|
|
return to_bool_json(v.IsNull());
|
|
}
|
|
},
|
|
{"attribute_type", [] (calculate_value_caller caller, const rjson::value* previous_item, const parsed::value::function_call& f) {
|
|
if (caller != calculate_value_caller::ConditionExpressionAlone) {
|
|
throw api_error::validation(
|
|
format("{}: attribute_type() not allowed here", caller));
|
|
}
|
|
if (f._parameters.size() != 2) {
|
|
throw api_error::validation(
|
|
format("{}: attribute_type() accepts 2 parameters, got {}", caller, f._parameters.size()));
|
|
}
|
|
// There is no real reason for the following check (not
|
|
// allowing the type to come from a document attribute), but
|
|
// DynamoDB does this check, so we do too...
|
|
if (!f._parameters[1].is_constant()) {
|
|
throw api_error::validation(
|
|
format("{}: attribute_types()'s first parameter must be an expression attribute", caller));
|
|
}
|
|
rjson::value v0 = calculate_value(f._parameters[0], caller, previous_item);
|
|
rjson::value v1 = calculate_value(f._parameters[1], caller, previous_item);
|
|
if (v1.IsObject() && v1.MemberCount() == 1 && v1.MemberBegin()->name == "S") {
|
|
// If the type parameter is not one of the legal types
|
|
// we should generate an error, not a failed condition:
|
|
if (!known_type(rjson::to_string_view(v1.MemberBegin()->value))) {
|
|
throw api_error::validation(
|
|
format("{}: attribute_types()'s second parameter, {}, is not a known type",
|
|
caller, v1.MemberBegin()->value));
|
|
}
|
|
if (v0.IsObject() && v0.MemberCount() == 1) {
|
|
return to_bool_json(v1.MemberBegin()->value == v0.MemberBegin()->name);
|
|
} else {
|
|
return to_bool_json(false);
|
|
}
|
|
} else {
|
|
throw api_error::validation(
|
|
format("{}: attribute_type() second parameter must refer to a string, got {}", caller, v1));
|
|
}
|
|
}
|
|
},
|
|
{"begins_with", [] (calculate_value_caller caller, const rjson::value* previous_item, const parsed::value::function_call& f) {
|
|
if (caller != calculate_value_caller::ConditionExpressionAlone) {
|
|
throw api_error::validation(
|
|
format("{}: begins_with() not allowed here", caller));
|
|
}
|
|
if (f._parameters.size() != 2) {
|
|
throw api_error::validation(
|
|
format("{}: begins_with() accepts 2 parameters, got {}", caller, f._parameters.size()));
|
|
}
|
|
rjson::value v1 = calculate_value(f._parameters[0], caller, previous_item);
|
|
rjson::value v2 = calculate_value(f._parameters[1], caller, previous_item);
|
|
// TODO: There's duplication here with check_BEGINS_WITH().
|
|
// But unfortunately, the two functions differ a bit.
|
|
|
|
// If one of v1 or v2 is malformed or has an unsupported type
|
|
// (not B or S), what we do depends on whether it came from
|
|
// the user's query (is_constant()), or the item. Unsupported
|
|
// values in the query result in an error, but if they are in
|
|
// the item, we silently return false (no match).
|
|
bool bad = false;
|
|
if (!v1.IsObject() || v1.MemberCount() != 1) {
|
|
bad = true;
|
|
if (f._parameters[0].is_constant()) {
|
|
throw api_error::validation(format("{}: begins_with() encountered malformed AttributeValue: {}", caller, v1));
|
|
}
|
|
} else if (v1.MemberBegin()->name != "S" && v1.MemberBegin()->name != "B") {
|
|
bad = true;
|
|
if (f._parameters[0].is_constant()) {
|
|
throw api_error::validation(format("{}: begins_with() supports only string or binary in AttributeValue: {}", caller, v1));
|
|
}
|
|
}
|
|
if (!v2.IsObject() || v2.MemberCount() != 1) {
|
|
bad = true;
|
|
if (f._parameters[1].is_constant()) {
|
|
throw api_error::validation(format("{}: begins_with() encountered malformed AttributeValue: {}", caller, v2));
|
|
}
|
|
} else if (v2.MemberBegin()->name != "S" && v2.MemberBegin()->name != "B") {
|
|
bad = true;
|
|
if (f._parameters[1].is_constant()) {
|
|
throw api_error::validation(format("{}: begins_with() supports only string or binary in AttributeValue: {}", caller, v2));
|
|
}
|
|
}
|
|
bool ret = false;
|
|
if (!bad) {
|
|
auto it1 = v1.MemberBegin();
|
|
auto it2 = v2.MemberBegin();
|
|
if (it1->name == it2->name) {
|
|
if (it2->name == "S") {
|
|
std::string_view val1 = rjson::to_string_view(it1->value);
|
|
std::string_view val2 = rjson::to_string_view(it2->value);
|
|
ret = val1.starts_with(val2);
|
|
} else /* it2->name == "B" */ {
|
|
ret = base64_begins_with(rjson::to_string_view(it1->value), rjson::to_string_view(it2->value));
|
|
}
|
|
}
|
|
}
|
|
return to_bool_json(ret);
|
|
}
|
|
},
|
|
{"contains", [] (calculate_value_caller caller, const rjson::value* previous_item, const parsed::value::function_call& f) {
|
|
if (caller != calculate_value_caller::ConditionExpressionAlone) {
|
|
throw api_error::validation(
|
|
format("{}: contains() not allowed here", caller));
|
|
}
|
|
if (f._parameters.size() != 2) {
|
|
throw api_error::validation(
|
|
format("{}: contains() accepts 2 parameters, got {}", caller, f._parameters.size()));
|
|
}
|
|
rjson::value v1 = calculate_value(f._parameters[0], caller, previous_item);
|
|
rjson::value v2 = calculate_value(f._parameters[1], caller, previous_item);
|
|
return to_bool_json(check_CONTAINS(v1.IsNull() ? nullptr : &v1, v2));
|
|
}
|
|
},
|
|
};
|
|
|
|
// Given a parsed::value, which can refer either to a constant value from
|
|
// ExpressionAttributeValues, to the value of some attribute, or to a function
|
|
// of other values, this function calculates the resulting value.
|
|
// "caller" determines which expression - ConditionExpression or
|
|
// UpdateExpression - is asking for this value. We need to know this because
|
|
// DynamoDB allows a different choice of functions for different expressions.
|
|
rjson::value calculate_value(const parsed::value& v,
|
|
calculate_value_caller caller,
|
|
const rjson::value* previous_item) {
|
|
return std::visit(overloaded_functor {
|
|
[&] (const parsed::constant& c) -> rjson::value {
|
|
return rjson::copy(calculate_value(c));
|
|
},
|
|
[&] (const parsed::value::function_call& f) -> rjson::value {
|
|
auto function_it = function_handlers.find(std::string_view(f._function_name));
|
|
if (function_it == function_handlers.end()) {
|
|
throw api_error::validation(
|
|
format("UpdateExpression: unknown function '{}' called.", f._function_name));
|
|
}
|
|
return function_it->second(caller, previous_item, f);
|
|
},
|
|
[&] (const parsed::path& p) -> rjson::value {
|
|
if (!previous_item) {
|
|
return rjson::null_value();
|
|
}
|
|
std::string update_path = p.root();
|
|
if (p.has_operators()) {
|
|
// FIXME: support this
|
|
throw api_error::validation("Reading attribute paths not yet implemented");
|
|
}
|
|
const rjson::value* previous_value = rjson::find(*previous_item, update_path);
|
|
return previous_value ? rjson::copy(*previous_value) : rjson::null_value();
|
|
}
|
|
}, v._value);
|
|
}
|
|
|
|
// Same as calculate_value() above, except takes a set_rhs, which may be
|
|
// either a single value, or v1+v2 or v1-v2.
|
|
rjson::value calculate_value(const parsed::set_rhs& rhs,
|
|
const rjson::value* previous_item) {
|
|
switch (rhs._op) {
|
|
case 'v':
|
|
return calculate_value(rhs._v1, calculate_value_caller::UpdateExpression, previous_item);
|
|
case '+': {
|
|
rjson::value v1 = calculate_value(rhs._v1, calculate_value_caller::UpdateExpression, previous_item);
|
|
rjson::value v2 = calculate_value(rhs._v2, calculate_value_caller::UpdateExpression, previous_item);
|
|
return number_add(v1, v2);
|
|
}
|
|
case '-': {
|
|
rjson::value v1 = calculate_value(rhs._v1, calculate_value_caller::UpdateExpression, previous_item);
|
|
rjson::value v2 = calculate_value(rhs._v2, calculate_value_caller::UpdateExpression, previous_item);
|
|
return number_subtract(v1, v2);
|
|
}
|
|
}
|
|
// Can't happen
|
|
return rjson::null_value();
|
|
}
|
|
|
|
} // namespace alternator
|