Compare commits
8 Commits
add-disk-s
...
T625-memgr
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e510ccf2b2 | ||
|
|
7d1b8100c7 | ||
|
|
ea494d4381 | ||
|
|
90d17f1bfd | ||
|
|
109150641c | ||
|
|
4366310691 | ||
|
|
1ae204551d | ||
|
|
e8fefb8842 |
@@ -666,11 +666,16 @@ class InListOperator : public memgraph::query::BinaryOperator {
|
||||
return object;
|
||||
}
|
||||
|
||||
void SetCachedSet(std::unordered_set<size_t> &&set_) { _cached_set.emplace(set_); }
|
||||
|
||||
std::unordered_set<size_t> *GetCachedSet() { return _cached_set.has_value() ? &*_cached_set : nullptr; }
|
||||
|
||||
protected:
|
||||
using BinaryOperator::BinaryOperator;
|
||||
|
||||
private:
|
||||
friend class AstStorage;
|
||||
std::optional<std::unordered_set<size_t>> _cached_set{std::nullopt};
|
||||
};
|
||||
|
||||
class SubscriptOperator : public memgraph::query::BinaryOperator {
|
||||
@@ -2933,6 +2938,7 @@ class LoadCsv : public memgraph::query::Clause {
|
||||
memgraph::query::Expression *file_;
|
||||
bool with_header_;
|
||||
bool ignore_bad_;
|
||||
bool ignore_empty_strings_;
|
||||
memgraph::query::Expression *delimiter_{nullptr};
|
||||
memgraph::query::Expression *quote_{nullptr};
|
||||
memgraph::query::Identifier *row_var_{nullptr};
|
||||
@@ -2942,6 +2948,7 @@ class LoadCsv : public memgraph::query::Clause {
|
||||
object->file_ = file_ ? file_->Clone(storage) : nullptr;
|
||||
object->with_header_ = with_header_;
|
||||
object->ignore_bad_ = ignore_bad_;
|
||||
object->ignore_empty_strings_ = ignore_empty_strings_;
|
||||
object->delimiter_ = delimiter_ ? delimiter_->Clone(storage) : nullptr;
|
||||
object->quote_ = quote_ ? quote_->Clone(storage) : nullptr;
|
||||
object->row_var_ = row_var_ ? row_var_->Clone(storage) : nullptr;
|
||||
@@ -2949,11 +2956,12 @@ class LoadCsv : public memgraph::query::Clause {
|
||||
}
|
||||
|
||||
protected:
|
||||
explicit LoadCsv(Expression *file, bool with_header, bool ignore_bad, Expression *delimiter, Expression *quote,
|
||||
Identifier *row_var)
|
||||
explicit LoadCsv(Expression *file, bool with_header, bool ignore_bad, bool ignore_empty_strings,
|
||||
Expression *delimiter, Expression *quote, Identifier *row_var)
|
||||
: file_(file),
|
||||
with_header_(with_header),
|
||||
ignore_bad_(ignore_bad),
|
||||
ignore_empty_strings_(ignore_empty_strings),
|
||||
delimiter_(delimiter),
|
||||
quote_(quote),
|
||||
row_var_(row_var) {
|
||||
|
||||
@@ -357,6 +357,8 @@ antlrcpp::Any CypherMainVisitor::visitLoadCsv(MemgraphCypher::LoadCsvContext *ct
|
||||
// handle skip bad row option
|
||||
load_csv->ignore_bad_ = ctx->IGNORE() && ctx->BAD();
|
||||
|
||||
load_csv->ignore_empty_strings_ = ctx->IGNORE_EMPTY_STRINGS();
|
||||
|
||||
// handle delimiter
|
||||
if (ctx->DELIMITER()) {
|
||||
if (ctx->delimiter()->literal()->StringLiteral()) {
|
||||
|
||||
@@ -58,6 +58,7 @@ memgraphCypherKeyword : cypherKeyword
|
||||
| GRANT
|
||||
| HEADER
|
||||
| IDENTIFIED
|
||||
| IGNORE_EMPTY_STRINGS
|
||||
| ISOLATION
|
||||
| IN_MEMORY_ANALYTICAL
|
||||
| IN_MEMORY_TRANSACTIONAL
|
||||
@@ -222,6 +223,7 @@ loadCsv : LOAD CSV FROM csvFile ( WITH | NO ) HEADER
|
||||
( IGNORE BAD ) ?
|
||||
( DELIMITER delimiter ) ?
|
||||
( QUOTE quote ) ?
|
||||
( IGNORE_EMPTY_STRINGS ) ?
|
||||
AS rowVar ;
|
||||
|
||||
csvFile : literal ;
|
||||
|
||||
@@ -68,7 +68,7 @@ GRAPH : G R A P H ;
|
||||
GRANTS : G R A N T S ;
|
||||
HEADER : H E A D E R ;
|
||||
IDENTIFIED : I D E N T I F I E D ;
|
||||
IGNORE : I G N O R E ;
|
||||
IGNORE_EMPTY_STRINGS : I G N O R E UNDERSCORE E M P T Y UNDERSCORE S T R I N G S;
|
||||
ISOLATION : I S O L A T I O N ;
|
||||
IN_MEMORY_ANALYTICAL : I N UNDERSCORE M E M O R Y UNDERSCORE A N A L Y T I C A L ;
|
||||
IN_MEMORY_TRANSACTIONAL : I N UNDERSCORE M E M O R Y UNDERSCORE T R A N S A C T I O N A L ;
|
||||
|
||||
@@ -190,17 +190,27 @@ class ExpressionEvaluator : public ExpressionVisitor<TypedValue> {
|
||||
}
|
||||
|
||||
TypedValue Visit(InListOperator &in_list) override {
|
||||
ReferenceExpressionEvaluator reference_expression_evaluator{frame_, symbol_table_, ctx_};
|
||||
|
||||
TypedValue *_list_ptr = in_list.expression2_->Accept(reference_expression_evaluator);
|
||||
TypedValue _list;
|
||||
|
||||
if (nullptr == _list_ptr) {
|
||||
_list = in_list.expression2_->Accept(*this);
|
||||
_list_ptr = &_list;
|
||||
}
|
||||
|
||||
auto literal = in_list.expression1_->Accept(*this);
|
||||
auto _list = in_list.expression2_->Accept(*this);
|
||||
if (_list.IsNull()) {
|
||||
|
||||
if (_list_ptr->IsNull()) {
|
||||
return TypedValue(ctx_->memory);
|
||||
}
|
||||
// Exceptions have higher priority than returning nulls when list expression
|
||||
// is not null.
|
||||
if (_list.type() != TypedValue::Type::List) {
|
||||
if (_list_ptr->type() != TypedValue::Type::List) {
|
||||
throw QueryRuntimeException("IN expected a list, got {}.", _list.type());
|
||||
}
|
||||
const auto &list = _list.ValueList();
|
||||
const auto &list = _list_ptr->ValueList();
|
||||
|
||||
// If literal is NULL there is no need to try to compare it with every
|
||||
// element in the list since result of every comparison will be NULL. There
|
||||
@@ -209,16 +219,24 @@ class ExpressionEvaluator : public ExpressionVisitor<TypedValue> {
|
||||
if (list.empty()) return TypedValue(false, ctx_->memory);
|
||||
if (literal.IsNull()) return TypedValue(ctx_->memory);
|
||||
|
||||
auto has_null = false;
|
||||
for (const auto &element : list) {
|
||||
auto result = literal == element;
|
||||
if (result.IsNull()) {
|
||||
has_null = true;
|
||||
} else if (result.ValueBool()) {
|
||||
return TypedValue(true, ctx_->memory);
|
||||
if (in_list.GetCachedSet() == nullptr) {
|
||||
std::unordered_set<size_t> _cached_set;
|
||||
TypedValue::Hash hash{};
|
||||
for (const TypedValue &element : list) {
|
||||
_cached_set.insert(hash(element));
|
||||
}
|
||||
|
||||
in_list.SetCachedSet(std::move(_cached_set));
|
||||
}
|
||||
if (has_null) {
|
||||
|
||||
const auto &in_list_cached_set = in_list.GetCachedSet();
|
||||
|
||||
TypedValue::Hash hash{};
|
||||
if (in_list_cached_set->contains(hash(literal))) {
|
||||
return TypedValue(true, ctx_->memory);
|
||||
}
|
||||
// has null
|
||||
if (literal.type() == TypedValue::Type::Null || in_list_cached_set->contains(hash(TypedValue(ctx_->memory)))) {
|
||||
return TypedValue(ctx_->memory);
|
||||
}
|
||||
return TypedValue(false, ctx_->memory);
|
||||
|
||||
@@ -4587,18 +4587,19 @@ UniqueCursorPtr CallProcedure::MakeCursor(utils::MemoryResource *mem) const {
|
||||
}
|
||||
|
||||
LoadCsv::LoadCsv(std::shared_ptr<LogicalOperator> input, Expression *file, bool with_header, bool ignore_bad,
|
||||
Expression *delimiter, Expression *quote, Symbol row_var)
|
||||
bool ignore_empty_strings, Expression *delimiter, Expression *quote, Symbol row_var)
|
||||
: input_(input ? input : (std::make_shared<Once>())),
|
||||
file_(file),
|
||||
with_header_(with_header),
|
||||
ignore_bad_(ignore_bad),
|
||||
ignore_empty_strings_(ignore_empty_strings),
|
||||
delimiter_(delimiter),
|
||||
quote_(quote),
|
||||
row_var_(row_var) {
|
||||
MG_ASSERT(file_, "Something went wrong - '{}' member file_ shouldn't be a nullptr", __func__);
|
||||
}
|
||||
|
||||
bool LoadCsv::Accept(HierarchicalLogicalOperatorVisitor &visitor) { return false; };
|
||||
ACCEPT_WITH_INPUT(LoadCsv)
|
||||
|
||||
class LoadCsvCursor;
|
||||
|
||||
@@ -4624,22 +4625,30 @@ auto ToOptionalString(ExpressionEvaluator *evaluator, Expression *expression) ->
|
||||
return std::nullopt;
|
||||
};
|
||||
|
||||
TypedValue CsvRowToTypedList(csv::Reader::Row &row) {
|
||||
TypedValue CsvRowToTypedList(csv::Reader::Row &row, bool ignore_empty_strings = false) {
|
||||
auto *mem = row.get_allocator().GetMemoryResource();
|
||||
auto typed_columns = utils::pmr::vector<TypedValue>(mem);
|
||||
typed_columns.reserve(row.size());
|
||||
for (auto &column : row) {
|
||||
typed_columns.emplace_back(std::move(column));
|
||||
if (!ignore_empty_strings || column.empty()) {
|
||||
typed_columns.emplace_back(std::move(column));
|
||||
} else {
|
||||
typed_columns.emplace_back(mem);
|
||||
}
|
||||
}
|
||||
return {std::move(typed_columns), mem};
|
||||
}
|
||||
|
||||
TypedValue CsvRowToTypedMap(csv::Reader::Row &row, csv::Reader::Header header) {
|
||||
TypedValue CsvRowToTypedMap(csv::Reader::Row &row, csv::Reader::Header header, bool ignore_empty_strings = false) {
|
||||
// a valid row has the same number of elements as the header
|
||||
auto *mem = row.get_allocator().GetMemoryResource();
|
||||
utils::pmr::map<utils::pmr::string, TypedValue> m(mem);
|
||||
for (auto i = 0; i < row.size(); ++i) {
|
||||
m.emplace(std::move(header[i]), std::move(row[i]));
|
||||
if (!ignore_empty_strings || !row[i].empty()) {
|
||||
m.emplace(std::move(header[i]), std::move(row[i]));
|
||||
} else {
|
||||
m.emplace(std::move(header[i]), mem);
|
||||
}
|
||||
}
|
||||
return {std::move(m), mem};
|
||||
}
|
||||
@@ -4649,14 +4658,12 @@ TypedValue CsvRowToTypedMap(csv::Reader::Row &row, csv::Reader::Header header) {
|
||||
class LoadCsvCursor : public Cursor {
|
||||
const LoadCsv *self_;
|
||||
const UniqueCursorPtr input_cursor_;
|
||||
bool input_is_once_;
|
||||
bool did_pull_;
|
||||
std::optional<csv::Reader> reader_{};
|
||||
|
||||
public:
|
||||
LoadCsvCursor(const LoadCsv *self, utils::MemoryResource *mem)
|
||||
: self_(self), input_cursor_(self_->input_->MakeCursor(mem)) {
|
||||
input_is_once_ = dynamic_cast<Once *>(self_->input_.get());
|
||||
}
|
||||
: self_(self), input_cursor_(self_->input_->MakeCursor(mem)), did_pull_{false} {}
|
||||
|
||||
bool Pull(Frame &frame, ExecutionContext &context) override {
|
||||
SCOPED_PROFILE_OP("LoadCsv");
|
||||
@@ -4674,12 +4681,14 @@ class LoadCsvCursor : public Cursor {
|
||||
|
||||
bool input_pulled = input_cursor_->Pull(frame, context);
|
||||
|
||||
// If the input is Once, we have to keep going until we read all the rows,
|
||||
// regardless of whether the pull on Once returned false.
|
||||
// If we have e.g. MATCH(n) LOAD CSV ... AS x SET n.name = x.name, then we
|
||||
// have to read at most cardinality(n) rows (but we can read less and stop
|
||||
// pulling MATCH).
|
||||
if (!input_is_once_ && !input_pulled) return false;
|
||||
if (input_pulled) {
|
||||
if (did_pull_) {
|
||||
throw QueryRuntimeException(
|
||||
"LOAD CSV can be executed only once, please check if the cardinality of the operator before LOAD CSV is 1");
|
||||
}
|
||||
did_pull_ = true;
|
||||
}
|
||||
|
||||
auto row = reader_->GetNextRow(context.evaluation_context.memory);
|
||||
if (!row) {
|
||||
return false;
|
||||
@@ -4688,7 +4697,8 @@ class LoadCsvCursor : public Cursor {
|
||||
frame[self_->row_var_] = CsvRowToTypedList(*row);
|
||||
} else {
|
||||
frame[self_->row_var_] =
|
||||
CsvRowToTypedMap(*row, csv::Reader::Header(reader_->GetHeader(), context.evaluation_context.memory));
|
||||
CsvRowToTypedMap(*row, csv::Reader::Header(reader_->GetHeader(), context.evaluation_context.memory),
|
||||
self_->ignore_empty_strings_);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -4712,10 +4722,10 @@ class LoadCsvCursor : public Cursor {
|
||||
// Note that the reader has to be given its own memory resource, as it
|
||||
// persists between pulls, so it can't use the evalutation context memory
|
||||
// resource.
|
||||
return csv::Reader(
|
||||
*maybe_file,
|
||||
csv::Reader::Config(self_->with_header_, self_->ignore_bad_, std::move(maybe_delim), std::move(maybe_quote)),
|
||||
utils::NewDeleteResource());
|
||||
return csv::Reader(*maybe_file,
|
||||
csv::Reader::Config(self_->with_header_, self_->ignore_bad_, self_->ignore_empty_strings_,
|
||||
std::move(maybe_delim), std::move(maybe_quote)),
|
||||
utils::NewDeleteResource());
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -2227,7 +2227,7 @@ class LoadCsv : public memgraph::query::plan::LogicalOperator {
|
||||
|
||||
LoadCsv() = default;
|
||||
LoadCsv(std::shared_ptr<LogicalOperator> input, Expression *file, bool with_header, bool ignore_bad,
|
||||
Expression *delimiter, Expression *quote, Symbol row_var);
|
||||
bool ignore_empty_strings, Expression *delimiter, Expression *quote, Symbol row_var);
|
||||
bool Accept(HierarchicalLogicalOperatorVisitor &visitor) override;
|
||||
UniqueCursorPtr MakeCursor(utils::MemoryResource *) const override;
|
||||
std::vector<Symbol> OutputSymbols(const SymbolTable &) const override;
|
||||
@@ -2241,6 +2241,7 @@ class LoadCsv : public memgraph::query::plan::LogicalOperator {
|
||||
Expression *file_;
|
||||
bool with_header_;
|
||||
bool ignore_bad_;
|
||||
bool ignore_empty_strings_;
|
||||
Expression *delimiter_{nullptr};
|
||||
Expression *quote_{nullptr};
|
||||
Symbol row_var_;
|
||||
@@ -2251,6 +2252,7 @@ class LoadCsv : public memgraph::query::plan::LogicalOperator {
|
||||
object->file_ = file_ ? file_->Clone(storage) : nullptr;
|
||||
object->with_header_ = with_header_;
|
||||
object->ignore_bad_ = ignore_bad_;
|
||||
object->ignore_empty_strings_ = ignore_empty_strings_;
|
||||
object->delimiter_ = delimiter_ ? delimiter_->Clone(storage) : nullptr;
|
||||
object->quote_ = quote_ ? quote_->Clone(storage) : nullptr;
|
||||
object->row_var_ = row_var_;
|
||||
|
||||
@@ -874,11 +874,27 @@ bool PlanToJsonVisitor::PreVisit(query::plan::CallProcedure &op) {
|
||||
bool PlanToJsonVisitor::PreVisit(query::plan::LoadCsv &op) {
|
||||
json self;
|
||||
self["name"] = "LoadCsv";
|
||||
self["file"] = ToJson(op.file_);
|
||||
self["with_header"] = op.with_header_;
|
||||
self["ignore_bad"] = op.ignore_bad_;
|
||||
self["delimiter"] = ToJson(op.delimiter_);
|
||||
self["quote"] = ToJson(op.quote_);
|
||||
|
||||
if (op.file_) {
|
||||
self["file"] = ToJson(op.file_);
|
||||
}
|
||||
|
||||
if (op.with_header_) {
|
||||
self["with_header"] = op.with_header_;
|
||||
}
|
||||
|
||||
if (op.ignore_bad_) {
|
||||
self["ignore_bad"] = op.ignore_bad_;
|
||||
}
|
||||
|
||||
if (op.delimiter_) {
|
||||
self["delimiter"] = ToJson(op.delimiter_);
|
||||
}
|
||||
|
||||
if (op.quote_) {
|
||||
self["quote"] = ToJson(op.quote_);
|
||||
}
|
||||
|
||||
self["row_variable"] = ToJson(op.row_var_);
|
||||
|
||||
op.input_->Accept(*this);
|
||||
|
||||
@@ -477,6 +477,16 @@ class IndexLookupRewriter final : public HierarchicalLogicalOperatorVisitor {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool PreVisit(LoadCsv &op) override {
|
||||
prev_ops_.push_back(&op);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool PostVisit(LoadCsv & /*op*/) override {
|
||||
prev_ops_.pop_back();
|
||||
return true;
|
||||
}
|
||||
|
||||
std::shared_ptr<LogicalOperator> new_root_;
|
||||
|
||||
private:
|
||||
|
||||
@@ -226,10 +226,9 @@ class RuleBasedPlanner {
|
||||
const auto &row_sym = context.symbol_table->at(*load_csv->row_var_);
|
||||
context.bound_symbols.insert(row_sym);
|
||||
|
||||
input_op =
|
||||
std::make_unique<plan::LoadCsv>(std::move(input_op), load_csv->file_, load_csv->with_header_,
|
||||
load_csv->ignore_bad_, load_csv->delimiter_, load_csv->quote_, row_sym);
|
||||
|
||||
input_op = std::make_unique<plan::LoadCsv>(std::move(input_op), load_csv->file_, load_csv->with_header_,
|
||||
load_csv->ignore_bad_, load_csv->ignore_empty_strings_,
|
||||
load_csv->delimiter_, load_csv->quote_, row_sym);
|
||||
} else if (auto *foreach = utils::Downcast<query::Foreach>(clause)) {
|
||||
context.is_write_query = true;
|
||||
input_op = HandleForeachClause(foreach, std::move(input_op), *context.symbol_table, context.bound_symbols,
|
||||
|
||||
@@ -170,7 +170,12 @@ bool CurrentVersionHasLabel(const Vertex &vertex, LabelId label, Transaction *tr
|
||||
deleted = vertex.deleted;
|
||||
has_label = utils::Contains(vertex.labels, label);
|
||||
delta = vertex.delta;
|
||||
|
||||
if (!vertex.label_changed) {
|
||||
return !deleted && has_label;
|
||||
}
|
||||
}
|
||||
|
||||
ApplyDeltasForRead(transaction, delta, view, [&deleted, &has_label, label](const Delta &delta) {
|
||||
switch (delta.action) {
|
||||
case Delta::Action::REMOVE_LABEL: {
|
||||
@@ -223,7 +228,12 @@ bool CurrentVersionHasLabelProperty(const Vertex &vertex, LabelId label, Propert
|
||||
has_label = utils::Contains(vertex.labels, label);
|
||||
current_value_equal_to_value = vertex.properties.IsPropertyEqual(key, value);
|
||||
delta = vertex.delta;
|
||||
|
||||
if (!vertex.label_changed && !vertex.property_changed) {
|
||||
return !deleted && has_label && current_value_equal_to_value;
|
||||
}
|
||||
}
|
||||
|
||||
ApplyDeltasForRead(transaction, delta, view,
|
||||
[&deleted, &has_label, ¤t_value_equal_to_value, key, label, &value](const Delta &delta) {
|
||||
switch (delta.action) {
|
||||
@@ -497,15 +507,31 @@ bool LabelPropertyIndex::Entry::operator<(const PropertyValue &rhs) { return val
|
||||
bool LabelPropertyIndex::Entry::operator==(const PropertyValue &rhs) { return value == rhs; }
|
||||
|
||||
void LabelPropertyIndex::UpdateOnAddLabel(LabelId label, Vertex *vertex, const Transaction &tx) {
|
||||
for (auto &[label_prop, storage] : index_) {
|
||||
if (label_prop.first != label) {
|
||||
continue;
|
||||
}
|
||||
auto prop_value = vertex->properties.GetProperty(label_prop.second);
|
||||
if (!prop_value.IsNull()) {
|
||||
auto vertex_properties = vertex->properties.Properties();
|
||||
|
||||
if (index_.size() > vertex_properties.size()) {
|
||||
for (auto &[prop_id, prop_value] : vertex_properties) {
|
||||
auto label_property_pair = std::make_pair(label, prop_id);
|
||||
if (index_.find(label_property_pair) == index_.end()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
auto &storage = index_[label_property_pair];
|
||||
auto acc = storage.access();
|
||||
acc.insert(Entry{std::move(prop_value), vertex, tx.start_timestamp});
|
||||
}
|
||||
} else {
|
||||
for (auto &[label_prop, storage] : index_) {
|
||||
if (label_prop.first != label) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (vertex_properties.find(label_prop.second) != vertex_properties.end()) {
|
||||
auto prop_value = vertex_properties[label_prop.second];
|
||||
auto acc = storage.access();
|
||||
acc.insert(Entry{std::move(prop_value), vertex, tx.start_timestamp});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -514,14 +540,31 @@ void LabelPropertyIndex::UpdateOnSetProperty(PropertyId property, const Property
|
||||
if (value.IsNull()) {
|
||||
return;
|
||||
}
|
||||
for (auto &[label_prop, storage] : index_) {
|
||||
if (label_prop.second != property) {
|
||||
continue;
|
||||
}
|
||||
if (utils::Contains(vertex->labels, label_prop.first)) {
|
||||
|
||||
auto index_size = index_.size();
|
||||
auto properties_size = vertex->properties.Properties().size();
|
||||
|
||||
if (index_size > properties_size) {
|
||||
for (auto &label : vertex->labels) {
|
||||
auto label_property_pair = std::make_pair(label, property);
|
||||
if (index_.find(label_property_pair) == index_.end()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
auto &storage = index_[label_property_pair];
|
||||
auto acc = storage.access();
|
||||
acc.insert(Entry{value, vertex, tx.start_timestamp});
|
||||
}
|
||||
} else {
|
||||
for (auto &[label_prop, storage] : index_) {
|
||||
if (label_prop.second != property) {
|
||||
continue;
|
||||
}
|
||||
if (utils::Contains(vertex->labels, label_prop.first)) {
|
||||
auto acc = storage.access();
|
||||
acc.insert(Entry{value, vertex, tx.start_timestamp});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1015,6 +1015,8 @@ void Storage::Accessor::Abort() {
|
||||
auto vertex = prev.vertex;
|
||||
std::lock_guard<utils::SpinLock> guard(vertex->lock);
|
||||
Delta *current = vertex->delta;
|
||||
vertex->label_changed = false;
|
||||
vertex->property_changed = false;
|
||||
while (current != nullptr && current->timestamp->load(std::memory_order_acquire) ==
|
||||
transaction_.transaction_id.load(std::memory_order_acquire)) {
|
||||
switch (current->action) {
|
||||
@@ -1551,6 +1553,8 @@ void Storage::CollectGarbage() {
|
||||
continue;
|
||||
}
|
||||
vertex->delta = nullptr;
|
||||
vertex->label_changed = false;
|
||||
vertex->property_changed = false;
|
||||
if (vertex->deleted) {
|
||||
current_deleted_vertices.push_back(vertex->gid);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2022 Memgraph Ltd.
|
||||
// Copyright 2023 Memgraph Ltd.
|
||||
//
|
||||
// Use of this software is governed by the Business Source License
|
||||
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
|
||||
@@ -39,8 +39,9 @@ struct Vertex {
|
||||
|
||||
mutable utils::SpinLock lock;
|
||||
bool deleted;
|
||||
bool label_changed;
|
||||
bool property_changed;
|
||||
// uint8_t PAD;
|
||||
// uint16_t PAD;
|
||||
|
||||
Delta *delta;
|
||||
};
|
||||
|
||||
@@ -85,6 +85,7 @@ Result<bool> VertexAccessor::AddLabel(LabelId label) {
|
||||
if (std::find(vertex_->labels.begin(), vertex_->labels.end(), label) != vertex_->labels.end()) return false;
|
||||
|
||||
CreateAndLinkDelta(transaction_, vertex_, Delta::RemoveLabelTag(), label);
|
||||
vertex_->label_changed = true;
|
||||
|
||||
vertex_->labels.push_back(label);
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2022 Memgraph Ltd.
|
||||
// Copyright 2023 Memgraph Ltd.
|
||||
//
|
||||
// Use of this software is governed by the Business Source License
|
||||
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
|
||||
@@ -123,7 +123,12 @@ Reader::ParsingResult Reader::ParseRow(utils::MemoryResource *mem) {
|
||||
line_string_view.remove_prefix(read_config_.quote->size());
|
||||
} else if (utils::StartsWith(line_string_view, *read_config_.delimiter)) {
|
||||
// The current field has an empty value.
|
||||
row.emplace_back("");
|
||||
if (read_config_.ignore_empty_strings) {
|
||||
row.emplace_back("\0");
|
||||
} else {
|
||||
row.emplace_back("");
|
||||
}
|
||||
|
||||
state = CsvParserState::NEXT_FIELD;
|
||||
line_string_view.remove_prefix(read_config_.delimiter->size());
|
||||
} else {
|
||||
@@ -183,7 +188,11 @@ Reader::ParsingResult Reader::ParseRow(utils::MemoryResource *mem) {
|
||||
case CsvParserState::EXPECT_DELIMITER:
|
||||
break;
|
||||
case CsvParserState::NEXT_FIELD:
|
||||
row.emplace_back("");
|
||||
if (read_config_.ignore_empty_strings) {
|
||||
row.emplace_back("\0");
|
||||
} else {
|
||||
row.emplace_back("");
|
||||
}
|
||||
break;
|
||||
case CsvParserState::QUOTING: {
|
||||
return ParseError(ParseError::ErrorCode::NO_CLOSING_QUOTE,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2022 Memgraph Ltd.
|
||||
// Copyright 2023 Memgraph Ltd.
|
||||
//
|
||||
// Use of this software is governed by the Business Source License
|
||||
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
|
||||
@@ -40,12 +40,17 @@ class Reader {
|
||||
public:
|
||||
struct Config {
|
||||
Config() = default;
|
||||
Config(const bool with_header, const bool ignore_bad, std::optional<utils::pmr::string> delim,
|
||||
std::optional<utils::pmr::string> qt)
|
||||
: with_header(with_header), ignore_bad(ignore_bad), delimiter(std::move(delim)), quote(std::move(qt)) {}
|
||||
Config(const bool with_header, const bool ignore_bad, const bool ignore_empty_strings,
|
||||
std::optional<utils::pmr::string> delim, std::optional<utils::pmr::string> qt)
|
||||
: with_header(with_header),
|
||||
ignore_bad(ignore_bad),
|
||||
ignore_empty_strings(ignore_empty_strings),
|
||||
delimiter(std::move(delim)),
|
||||
quote(std::move(qt)) {}
|
||||
|
||||
bool with_header{false};
|
||||
bool ignore_bad{false};
|
||||
bool ignore_empty_strings{false};
|
||||
std::optional<utils::pmr::string> delimiter{};
|
||||
std::optional<utils::pmr::string> quote{};
|
||||
};
|
||||
@@ -58,6 +63,7 @@ class Reader {
|
||||
: memory_(mem), path_(std::move(path)) {
|
||||
read_config_.with_header = cfg.with_header;
|
||||
read_config_.ignore_bad = cfg.ignore_bad;
|
||||
read_config_.ignore_empty_strings = cfg.ignore_empty_strings;
|
||||
read_config_.delimiter = cfg.delimiter ? std::move(*cfg.delimiter) : utils::pmr::string{",", memory_};
|
||||
read_config_.quote = cfg.quote ? std::move(*cfg.quote) : utils::pmr::string{"\"", memory_};
|
||||
InitializeStream();
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2022 Memgraph Ltd.
|
||||
// Copyright 2023 Memgraph Ltd.
|
||||
//
|
||||
// Use of this software is governed by the Business Source License
|
||||
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
|
||||
@@ -97,10 +97,11 @@ TEST_P(CsvReaderTest, CommaDelimiter) {
|
||||
|
||||
bool with_header = false;
|
||||
bool ignore_bad = false;
|
||||
bool ignore_empty_strings = false;
|
||||
memgraph::utils::pmr::string delimiter{",", mem};
|
||||
memgraph::utils::pmr::string quote{"\"", mem};
|
||||
|
||||
memgraph::csv::Reader::Config cfg{with_header, ignore_bad, delimiter, quote};
|
||||
memgraph::csv::Reader::Config cfg{with_header, ignore_bad, ignore_empty_strings, delimiter, quote};
|
||||
auto reader = memgraph::csv::Reader(filepath, cfg, mem);
|
||||
|
||||
auto parsed_row = reader.GetNextRow(mem);
|
||||
@@ -123,7 +124,8 @@ TEST_P(CsvReaderTest, SemicolonDelimiter) {
|
||||
|
||||
const bool with_header = false;
|
||||
const bool ignore_bad = false;
|
||||
const memgraph::csv::Reader::Config cfg{with_header, ignore_bad, delimiter, quote};
|
||||
const bool ignore_empty_strings = false;
|
||||
const memgraph::csv::Reader::Config cfg{with_header, ignore_bad, ignore_empty_strings, delimiter, quote};
|
||||
auto reader = memgraph::csv::Reader(filepath, cfg, mem);
|
||||
|
||||
auto parsed_row = reader.GetNextRow(mem);
|
||||
@@ -156,7 +158,8 @@ TEST_P(CsvReaderTest, SkipBad) {
|
||||
// parser's output should be solely the valid row;
|
||||
const bool with_header = false;
|
||||
const bool ignore_bad = true;
|
||||
const memgraph::csv::Reader::Config cfg{with_header, ignore_bad, delimiter, quote};
|
||||
const bool ignore_empty_strings = false;
|
||||
const memgraph::csv::Reader::Config cfg{with_header, ignore_bad, ignore_empty_strings, delimiter, quote};
|
||||
auto reader = memgraph::csv::Reader(filepath, cfg, mem);
|
||||
|
||||
auto parsed_row = reader.GetNextRow(mem);
|
||||
@@ -168,7 +171,8 @@ TEST_P(CsvReaderTest, SkipBad) {
|
||||
// an exception must be thrown;
|
||||
const bool with_header = false;
|
||||
const bool ignore_bad = false;
|
||||
const memgraph::csv::Reader::Config cfg{with_header, ignore_bad, delimiter, quote};
|
||||
const bool ignore_empty_strings = false;
|
||||
const memgraph::csv::Reader::Config cfg{with_header, ignore_bad, ignore_empty_strings, delimiter, quote};
|
||||
auto reader = memgraph::csv::Reader(filepath, cfg, mem);
|
||||
|
||||
EXPECT_THROW(reader.GetNextRow(mem), memgraph::csv::CsvReadException);
|
||||
@@ -195,7 +199,8 @@ TEST_P(CsvReaderTest, AllRowsValid) {
|
||||
|
||||
const bool with_header = false;
|
||||
const bool ignore_bad = false;
|
||||
const memgraph::csv::Reader::Config cfg{with_header, ignore_bad, delimiter, quote};
|
||||
const bool ignore_empty_strings = false;
|
||||
const memgraph::csv::Reader::Config cfg{with_header, ignore_bad, ignore_empty_strings, delimiter, quote};
|
||||
auto reader = memgraph::csv::Reader(filepath, cfg);
|
||||
|
||||
const auto pmr_columns = ToPmrColumns(columns);
|
||||
@@ -224,7 +229,8 @@ TEST_P(CsvReaderTest, SkipAllRows) {
|
||||
|
||||
const bool with_header = false;
|
||||
const bool ignore_bad = true;
|
||||
const memgraph::csv::Reader::Config cfg{with_header, ignore_bad, delimiter, quote};
|
||||
const bool ignore_empty_strings = false;
|
||||
const memgraph::csv::Reader::Config cfg{with_header, ignore_bad, ignore_empty_strings, delimiter, quote};
|
||||
auto reader = memgraph::csv::Reader(filepath, cfg);
|
||||
|
||||
auto parsed_row = reader.GetNextRow(mem);
|
||||
@@ -251,7 +257,8 @@ TEST_P(CsvReaderTest, WithHeader) {
|
||||
|
||||
const bool with_header = true;
|
||||
const bool ignore_bad = false;
|
||||
const memgraph::csv::Reader::Config cfg(with_header, ignore_bad, delimiter, quote);
|
||||
const bool ignore_empty_strings = false;
|
||||
const memgraph::csv::Reader::Config cfg(with_header, ignore_bad, ignore_empty_strings, delimiter, quote);
|
||||
auto reader = memgraph::csv::Reader(filepath, cfg);
|
||||
|
||||
const auto pmr_header = ToPmrColumns(header);
|
||||
@@ -287,7 +294,8 @@ TEST_P(CsvReaderTest, MultilineQuotedString) {
|
||||
|
||||
const bool with_header = false;
|
||||
const bool ignore_bad = true;
|
||||
const memgraph::csv::Reader::Config cfg{with_header, ignore_bad, delimiter, quote};
|
||||
const bool ignore_empty_strings = false;
|
||||
const memgraph::csv::Reader::Config cfg{with_header, ignore_bad, ignore_empty_strings, delimiter, quote};
|
||||
auto reader = memgraph::csv::Reader(filepath, cfg);
|
||||
|
||||
auto parsed_row = reader.GetNextRow(mem);
|
||||
@@ -319,7 +327,8 @@ TEST_P(CsvReaderTest, EmptyColumns) {
|
||||
|
||||
const bool with_header = false;
|
||||
const bool ignore_bad = false;
|
||||
const memgraph::csv::Reader::Config cfg{with_header, ignore_bad, delimiter, quote};
|
||||
const bool ignore_empty_strings = false;
|
||||
const memgraph::csv::Reader::Config cfg{with_header, ignore_bad, ignore_empty_strings, delimiter, quote};
|
||||
auto reader = memgraph::csv::Reader(filepath, cfg);
|
||||
|
||||
for (const auto &expected_row : expected_rows) {
|
||||
|
||||
Reference in New Issue
Block a user