Counter function added
Reviewers: buda, mferencevic, mislav.bradac Reviewed By: mislav.bradac Subscribers: pullbot Differential Revision: https://phabricator.memgraph.io/D787
This commit is contained in:
parent
9d63dfa9ca
commit
bfbec8d550
@ -11,6 +11,7 @@
|
|||||||
* Map indexing supported.
|
* Map indexing supported.
|
||||||
* `assert` function added.
|
* `assert` function added.
|
||||||
* Use \u to specify 4 digit codepoint and \U for 8 digit
|
* Use \u to specify 4 digit codepoint and \U for 8 digit
|
||||||
|
* `counter` function added.
|
||||||
|
|
||||||
### Bug Fixes and Other Changes
|
### Bug Fixes and Other Changes
|
||||||
|
|
||||||
|
@ -500,6 +500,7 @@ functions.
|
|||||||
`contains` | Check if the first argument has an element which is equal to the second argument.
|
`contains` | Check if the first argument has an element which is equal to the second argument.
|
||||||
`all` | Check if all elements of a list satisfy a predicate.<br/>The syntax is: `all(variable IN list WHERE predicate)`.
|
`all` | Check if all elements of a list satisfy a predicate.<br/>The syntax is: `all(variable IN list WHERE predicate)`.
|
||||||
`assert` | Raises an exception reported to the client if the given argument is not `true`.
|
`assert` | Raises an exception reported to the client if the given argument is not `true`.
|
||||||
|
`counter` | Generates integers that are guaranteed to be unique on the database level, for the given counter name.
|
||||||
|
|
||||||
#### String Operators
|
#### String Operators
|
||||||
|
|
||||||
|
@ -5,6 +5,7 @@
|
|||||||
#include "cppitertools/filter.hpp"
|
#include "cppitertools/filter.hpp"
|
||||||
#include "cppitertools/imap.hpp"
|
#include "cppitertools/imap.hpp"
|
||||||
|
|
||||||
|
#include "data_structures/concurrent/concurrent_map.hpp"
|
||||||
#include "data_structures/concurrent/concurrent_set.hpp"
|
#include "data_structures/concurrent/concurrent_set.hpp"
|
||||||
#include "data_structures/concurrent/skiplist.hpp"
|
#include "data_structures/concurrent/skiplist.hpp"
|
||||||
#include "database/graph_db_datatypes.hpp"
|
#include "database/graph_db_datatypes.hpp"
|
||||||
@ -120,4 +121,7 @@ class GraphDb {
|
|||||||
// Periodically wakes up and hints to transactions that are running for a long
|
// Periodically wakes up and hints to transactions that are running for a long
|
||||||
// time to stop their execution.
|
// time to stop their execution.
|
||||||
Scheduler transaction_killer_;
|
Scheduler transaction_killer_;
|
||||||
|
|
||||||
|
// DB level global counters, used in the "counter" function
|
||||||
|
ConcurrentMap<std::string, std::atomic<int64_t>> counters_;
|
||||||
};
|
};
|
||||||
|
@ -328,3 +328,9 @@ const std::string &GraphDbAccessor::PropertyName(
|
|||||||
debug_assert(!commited_ && !aborted_, "Accessor committed or aborted");
|
debug_assert(!commited_ && !aborted_, "Accessor committed or aborted");
|
||||||
return *property;
|
return *property;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int64_t GraphDbAccessor::Counter(const std::string &name) {
|
||||||
|
return db_.counters_.access()
|
||||||
|
.emplace(name, std::make_tuple(name), std::make_tuple(0))
|
||||||
|
.first->second.fetch_add(1);
|
||||||
|
}
|
||||||
|
@ -561,6 +561,13 @@ class GraphDbAccessor {
|
|||||||
if (!accessor.new_) accessor.new_ = accessor.vlist_->update(*transaction_);
|
if (!accessor.new_) accessor.new_ = accessor.vlist_->update(*transaction_);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the current value of the counter with the given name, and
|
||||||
|
* increments that counter. If the counter with the given name does not exist,
|
||||||
|
* a new counter is created and this function returns 0.
|
||||||
|
*/
|
||||||
|
int64_t Counter(const std::string &name);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
/**
|
/**
|
||||||
* Insert this vertex into corresponding label and label+property (if it
|
* Insert this vertex into corresponding label and label+property (if it
|
||||||
|
@ -512,18 +512,27 @@ TypedValue Assert(const std::vector<TypedValue> &args, GraphDbAccessor &) {
|
|||||||
throw QueryRuntimeException("assert takes one or two arguments");
|
throw QueryRuntimeException("assert takes one or two arguments");
|
||||||
}
|
}
|
||||||
if (args[0].type() != TypedValue::Type::Bool)
|
if (args[0].type() != TypedValue::Type::Bool)
|
||||||
throw QueryRuntimeException("first assert argument must be bool");
|
throw QueryRuntimeException("first assert argument must be bool");
|
||||||
if (args.size() == 2U && args[1].type() != TypedValue::Type::String)
|
if (args.size() == 2U && args[1].type() != TypedValue::Type::String)
|
||||||
throw QueryRuntimeException("second assert argument must be a string");
|
throw QueryRuntimeException("second assert argument must be a string");
|
||||||
if (!args[0].ValueBool()) {
|
if (!args[0].ValueBool()) {
|
||||||
std::string message("assertion failed");
|
std::string message("assertion failed");
|
||||||
if (args.size() == 2U)
|
if (args.size() == 2U) message += ": " + args[1].ValueString();
|
||||||
message += ": " + args[1].ValueString();
|
|
||||||
throw QueryRuntimeException(message);
|
throw QueryRuntimeException(message);
|
||||||
}
|
}
|
||||||
return args[0];
|
return args[0];
|
||||||
}
|
}
|
||||||
} // annonymous namespace
|
|
||||||
|
TypedValue Counter(const std::vector<TypedValue> &args, GraphDbAccessor &dba) {
|
||||||
|
if (args.size() != 1U) {
|
||||||
|
throw QueryRuntimeException("counter takes one argument");
|
||||||
|
}
|
||||||
|
if (!args[0].IsString())
|
||||||
|
throw QueryRuntimeException("first counter argument must be a string");
|
||||||
|
|
||||||
|
return dba.Counter(args[0].ValueString());
|
||||||
|
}
|
||||||
|
} // annonymous namespace
|
||||||
|
|
||||||
std::function<TypedValue(const std::vector<TypedValue> &, GraphDbAccessor &)>
|
std::function<TypedValue(const std::vector<TypedValue> &, GraphDbAccessor &)>
|
||||||
NameToFunction(const std::string &function_name) {
|
NameToFunction(const std::string &function_name) {
|
||||||
@ -566,6 +575,7 @@ NameToFunction(const std::string &function_name) {
|
|||||||
if (function_name == kEndsWith) return EndsWith;
|
if (function_name == kEndsWith) return EndsWith;
|
||||||
if (function_name == kContains) return Contains;
|
if (function_name == kContains) return Contains;
|
||||||
if (function_name == "ASSERT") return Assert;
|
if (function_name == "ASSERT") return Assert;
|
||||||
|
if (function_name == "COUNTER") return Counter;
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
} // namespace query
|
} // namespace query
|
||||||
|
@ -690,3 +690,20 @@ Feature: Functions
|
|||||||
Then the result should be:
|
Then the result should be:
|
||||||
| res |
|
| res |
|
||||||
| true |
|
| true |
|
||||||
|
|
||||||
|
Scenario: Counter test:
|
||||||
|
Given an empty graph
|
||||||
|
And having executed:
|
||||||
|
"""
|
||||||
|
CREATE (), (), ()
|
||||||
|
"""
|
||||||
|
When executing query:
|
||||||
|
"""
|
||||||
|
MATCH (n) SET n.id = counter("n.id") WITH n SKIP 1
|
||||||
|
RETURN n.id, counter("other") AS c2
|
||||||
|
"""
|
||||||
|
Then the result should be:
|
||||||
|
| n.id | c2 |
|
||||||
|
| 1 | 0 |
|
||||||
|
| 2 | 1 |
|
||||||
|
|
||||||
|
@ -36,11 +36,13 @@ struct NoContextExpressionEvaluator {
|
|||||||
};
|
};
|
||||||
|
|
||||||
TypedValue EvaluateFunction(const std::string &function_name,
|
TypedValue EvaluateFunction(const std::string &function_name,
|
||||||
const std::vector<TypedValue> &args) {
|
const std::vector<TypedValue> &args, Dbms &dbms) {
|
||||||
AstTreeStorage storage;
|
AstTreeStorage storage;
|
||||||
NoContextExpressionEvaluator eval;
|
SymbolTable symbol_table;
|
||||||
Dbms dbms;
|
|
||||||
auto dba = dbms.active();
|
auto dba = dbms.active();
|
||||||
|
Frame frame{128};
|
||||||
|
Parameters parameters;
|
||||||
|
ExpressionEvaluator eval{frame, parameters, symbol_table, *dba};
|
||||||
|
|
||||||
std::vector<Expression *> expressions;
|
std::vector<Expression *> expressions;
|
||||||
for (const auto &arg : args) {
|
for (const auto &arg : args) {
|
||||||
@ -48,7 +50,13 @@ TypedValue EvaluateFunction(const std::string &function_name,
|
|||||||
}
|
}
|
||||||
auto *op =
|
auto *op =
|
||||||
storage.Create<Function>(NameToFunction(function_name), expressions);
|
storage.Create<Function>(NameToFunction(function_name), expressions);
|
||||||
return op->Accept(eval.eval);
|
return op->Accept(eval);
|
||||||
|
}
|
||||||
|
|
||||||
|
TypedValue EvaluateFunction(const std::string &function_name,
|
||||||
|
const std::vector<TypedValue> &args) {
|
||||||
|
Dbms dbms;
|
||||||
|
return EvaluateFunction(function_name, args, dbms);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(ExpressionEvaluator, OrOperator) {
|
TEST(ExpressionEvaluator, OrOperator) {
|
||||||
@ -1166,13 +1174,17 @@ TEST(ExpressionEvaluator, FunctionAllWhereWrongType) {
|
|||||||
TEST(ExpressionEvaluator, FunctionAssert) {
|
TEST(ExpressionEvaluator, FunctionAssert) {
|
||||||
// Invalid calls.
|
// Invalid calls.
|
||||||
ASSERT_THROW(EvaluateFunction("ASSERT", {}), QueryRuntimeException);
|
ASSERT_THROW(EvaluateFunction("ASSERT", {}), QueryRuntimeException);
|
||||||
ASSERT_THROW(EvaluateFunction("ASSERT", {false, false}), QueryRuntimeException);
|
ASSERT_THROW(EvaluateFunction("ASSERT", {false, false}),
|
||||||
ASSERT_THROW(EvaluateFunction("ASSERT", {"string", false}), QueryRuntimeException);
|
QueryRuntimeException);
|
||||||
ASSERT_THROW(EvaluateFunction("ASSERT", {false, "reason", true}), QueryRuntimeException);
|
ASSERT_THROW(EvaluateFunction("ASSERT", {"string", false}),
|
||||||
|
QueryRuntimeException);
|
||||||
|
ASSERT_THROW(EvaluateFunction("ASSERT", {false, "reason", true}),
|
||||||
|
QueryRuntimeException);
|
||||||
|
|
||||||
// Valid calls, assertion fails.
|
// Valid calls, assertion fails.
|
||||||
ASSERT_THROW(EvaluateFunction("ASSERT", {false}), QueryRuntimeException);
|
ASSERT_THROW(EvaluateFunction("ASSERT", {false}), QueryRuntimeException);
|
||||||
ASSERT_THROW(EvaluateFunction("ASSERT", {false, "message"}), QueryRuntimeException);
|
ASSERT_THROW(EvaluateFunction("ASSERT", {false, "message"}),
|
||||||
|
QueryRuntimeException);
|
||||||
try {
|
try {
|
||||||
EvaluateFunction("ASSERT", {false, "bbgba"});
|
EvaluateFunction("ASSERT", {false, "bbgba"});
|
||||||
} catch (QueryRuntimeException &e) {
|
} catch (QueryRuntimeException &e) {
|
||||||
@ -1194,4 +1206,16 @@ TEST(ExpressionEvaluator, ParameterLookup) {
|
|||||||
EXPECT_EQ(value.Value<int64_t>(), 42);
|
EXPECT_EQ(value.Value<int64_t>(), 42);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST(ExpressionEvaluator, FunctionCounter) {
|
||||||
|
Dbms dbms;
|
||||||
|
EXPECT_THROW(EvaluateFunction("COUNTER", {}, dbms), QueryRuntimeException);
|
||||||
|
EXPECT_THROW(EvaluateFunction("COUNTER", {"a", "b"}, dbms),
|
||||||
|
QueryRuntimeException);
|
||||||
|
EXPECT_EQ(EvaluateFunction("COUNTER", {"c1"}, dbms).ValueInt(), 0);
|
||||||
|
EXPECT_EQ(EvaluateFunction("COUNTER", {"c1"}, dbms).ValueInt(), 1);
|
||||||
|
EXPECT_EQ(EvaluateFunction("COUNTER", {"c2"}, dbms).ValueInt(), 0);
|
||||||
|
EXPECT_EQ(EvaluateFunction("COUNTER", {"c1"}, dbms).ValueInt(), 2);
|
||||||
|
EXPECT_EQ(EvaluateFunction("COUNTER", {"c2"}, dbms).ValueInt(), 1);
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
Loading…
Reference in New Issue
Block a user