mirror of https://gitee.com/bigwinds/arangodb
812 lines
29 KiB
C++
812 lines
29 KiB
C++
////////////////////////////////////////////////////////////////////////////////
|
|
/// DISCLAIMER
|
|
///
|
|
/// Copyright 2019 ArangoDB GmbH, Cologne, Germany
|
|
///
|
|
/// Licensed 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 holder is ArangoDB GmbH, Cologne, Germany
|
|
///
|
|
/// @author Michael Hackstein
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
#include "Mocks/Servers.h"
|
|
#include "QueryHelper.h"
|
|
#include "gtest/gtest.h"
|
|
|
|
#include "Basics/StringUtils.h"
|
|
|
|
using namespace arangodb;
|
|
using namespace arangodb::aql;
|
|
|
|
namespace arangodb {
|
|
namespace tests {
|
|
namespace aql {
|
|
|
|
static const std::string GetAllDocs =
|
|
R"aql(FOR doc IN UnitTestCollection SORT doc.sortValue RETURN doc.value)aql";
|
|
|
|
enum UpsertType { UPDATE, REPLACE };
|
|
|
|
class UpsertExecutorTest : public ::testing::TestWithParam<UpsertType> {
|
|
protected:
|
|
mocks::MockAqlServer server;
|
|
TRI_vocbase_t& vocbase{server.getSystemDatabase()};
|
|
|
|
void SetUp() override {
|
|
SCOPED_TRACE("Setup");
|
|
auto info = VPackParser::fromJson(R"({"name":"UnitTestCollection"})");
|
|
auto collection = vocbase.createCollection(info->slice());
|
|
ASSERT_NE(collection.get(), nullptr) << "Failed to create collection";
|
|
// Insert Documents
|
|
std::string insertQuery =
|
|
R"aql(INSERT {_key: "testee", value: 1, sortValue: 1, nestedObject: {value: 1} } INTO UnitTestCollection)aql";
|
|
SCOPED_TRACE(insertQuery);
|
|
AssertQueryHasResult(vocbase, insertQuery, VPackSlice::emptyArraySlice());
|
|
auto expected = VPackParser::fromJson(R"([1])");
|
|
AssertQueryHasResult(vocbase, GetAllDocs, expected->slice());
|
|
}
|
|
|
|
void AssertNotChanged() {
|
|
auto expected = VPackParser::fromJson(R"([1])");
|
|
AssertQueryHasResult(vocbase, GetAllDocs, expected->slice());
|
|
}
|
|
|
|
std::string const action() const {
|
|
if (GetParam() == UpsertType::UPDATE) {
|
|
return "UPDATE";
|
|
}
|
|
return "REPLACE";
|
|
}
|
|
};
|
|
|
|
TEST_P(UpsertExecutorTest, basic) {
|
|
std::string query = R"aql(
|
|
UPSERT {_key: "testee"}
|
|
INSERT {value: "invalid"})aql" +
|
|
action() + R"aql({value: 2}
|
|
INTO UnitTestCollection)aql";
|
|
AssertQueryHasResult(vocbase, query, VPackSlice::emptyArraySlice());
|
|
|
|
auto expected = VPackParser::fromJson(R"([2])");
|
|
AssertQueryHasResult(vocbase, GetAllDocs, expected->slice());
|
|
}
|
|
|
|
TEST_P(UpsertExecutorTest, option_ignoreErrors_default) {
|
|
// This should trigger a duplicate key error
|
|
std::string query = R"aql(
|
|
UPSERT {value: "thiscannotbefound"}
|
|
INSERT {_key: "testee", value: 2}
|
|
)aql" + action() +
|
|
R"aql( {value: 2}
|
|
INTO UnitTestCollection)aql";
|
|
AssertQueryFailsWith(vocbase, query, TRI_ERROR_ARANGO_UNIQUE_CONSTRAINT_VIOLATED);
|
|
AssertNotChanged();
|
|
}
|
|
|
|
TEST_P(UpsertExecutorTest, option_ignoreErrors_true) {
|
|
// This should trigger a duplicate key error
|
|
std::string query = R"aql(
|
|
UPSERT {value: "thiscannotbefound"}
|
|
INSERT {_key: "testee", value: 2}
|
|
)aql" + action() +
|
|
R"aql( {value: 2}
|
|
INTO UnitTestCollection
|
|
OPTIONS {ignoreErrors: true})aql";
|
|
AssertQueryHasResult(vocbase, query, VPackSlice::emptyArraySlice());
|
|
AssertNotChanged();
|
|
}
|
|
|
|
TEST_P(UpsertExecutorTest, option_ignoreErrors_false) {
|
|
// This should trigger a duplicate key error
|
|
std::string query = R"aql(
|
|
UPSERT {value: "thiscannotbefound"}
|
|
INSERT {_key: "testee", value: 2}
|
|
)aql" + action() +
|
|
R"aql( {value: 2}
|
|
INTO UnitTestCollection
|
|
OPTIONS {ignoreErrors: false})aql";
|
|
AssertQueryFailsWith(vocbase, query, TRI_ERROR_ARANGO_UNIQUE_CONSTRAINT_VIOLATED);
|
|
AssertNotChanged();
|
|
}
|
|
|
|
TEST_P(UpsertExecutorTest, option_keepNull_default) {
|
|
std::string query = R"aql(
|
|
UPSERT {_key: "testee"}
|
|
INSERT {value: "invalid"}
|
|
)aql" + action() +
|
|
R"aql( {value: null}
|
|
INTO UnitTestCollection)aql";
|
|
AssertQueryHasResult(vocbase, query, VPackSlice::emptyArraySlice());
|
|
std::string testQuery =
|
|
R"aql(FOR x IN UnitTestCollection FILTER x._key == "testee" RETURN HAS(x, "value"))aql";
|
|
auto expected = VPackParser::fromJson(R"([true])");
|
|
AssertQueryHasResult(vocbase, testQuery, expected->slice());
|
|
}
|
|
|
|
TEST_P(UpsertExecutorTest, option_keepNull_true) {
|
|
std::string query = R"aql(
|
|
UPSERT {_key: "testee"}
|
|
INSERT {value: "invalid"}
|
|
)aql" + action() +
|
|
R"aql( {value: null}
|
|
INTO UnitTestCollection
|
|
OPTIONS {keepNull: true})aql";
|
|
AssertQueryHasResult(vocbase, query, VPackSlice::emptyArraySlice());
|
|
std::string testQuery =
|
|
R"aql(FOR x IN UnitTestCollection FILTER x._key == "testee" RETURN HAS(x, "value"))aql";
|
|
auto expected = VPackParser::fromJson(R"([true])");
|
|
AssertQueryHasResult(vocbase, testQuery, expected->slice());
|
|
}
|
|
|
|
TEST_P(UpsertExecutorTest, option_keepNull_false) {
|
|
std::string query = R"aql(
|
|
UPSERT {_key: "testee"}
|
|
INSERT {value: "invalid"}
|
|
)aql" + action() +
|
|
R"aql( {value: null}
|
|
INTO UnitTestCollection
|
|
OPTIONS {keepNull: false})aql";
|
|
AssertQueryHasResult(vocbase, query, VPackSlice::emptyArraySlice());
|
|
std::string testQuery =
|
|
R"aql(FOR x IN UnitTestCollection FILTER x._key == "testee" RETURN HAS(x, "value"))aql";
|
|
if (GetParam() == UpsertType::UPDATE) {
|
|
auto expected = VPackParser::fromJson(R"([false])");
|
|
AssertQueryHasResult(vocbase, testQuery, expected->slice());
|
|
} else {
|
|
// Replace will not honor keepNull
|
|
auto expected = VPackParser::fromJson(R"([true])");
|
|
AssertQueryHasResult(vocbase, testQuery, expected->slice());
|
|
}
|
|
}
|
|
|
|
TEST_P(UpsertExecutorTest, option_mergeObjects_default) {
|
|
std::string query = R"aql(
|
|
UPSERT {_key: "testee"}
|
|
INSERT {value: "invalid"}
|
|
)aql" + action() +
|
|
R"aql( {nestedObject: {foo: "bar"} }
|
|
INTO UnitTestCollection)aql";
|
|
AssertQueryHasResult(vocbase, query, VPackSlice::emptyArraySlice());
|
|
std::string testQuery =
|
|
R"aql(FOR x IN UnitTestCollection FILTER x._key == "testee" RETURN x.nestedObject)aql";
|
|
if (GetParam() == UpsertType::UPDATE) {
|
|
auto expected = VPackParser::fromJson(R"([{"foo": "bar", "value": 1}])");
|
|
AssertQueryHasResult(vocbase, testQuery, expected->slice());
|
|
} else {
|
|
// Replace will never merge
|
|
auto expected = VPackParser::fromJson(R"([{"foo": "bar"}])");
|
|
AssertQueryHasResult(vocbase, testQuery, expected->slice());
|
|
}
|
|
}
|
|
|
|
TEST_P(UpsertExecutorTest, option_mergeObjects_true) {
|
|
std::string query = R"aql(
|
|
UPSERT {_key: "testee"}
|
|
INSERT {value: "invalid"}
|
|
)aql" + action() +
|
|
R"aql( {nestedObject: {foo: "bar"} }
|
|
INTO UnitTestCollection
|
|
OPTIONS {mergeObjects: true})aql";
|
|
AssertQueryHasResult(vocbase, query, VPackSlice::emptyArraySlice());
|
|
std::string testQuery =
|
|
R"aql(FOR x IN UnitTestCollection FILTER x._key == "testee" RETURN x.nestedObject)aql";
|
|
if (GetParam() == UpsertType::UPDATE) {
|
|
auto expected = VPackParser::fromJson(R"([{"foo": "bar", "value": 1}])");
|
|
AssertQueryHasResult(vocbase, testQuery, expected->slice());
|
|
} else {
|
|
// Replace will never merge
|
|
auto expected = VPackParser::fromJson(R"([{"foo": "bar"}])");
|
|
AssertQueryHasResult(vocbase, testQuery, expected->slice());
|
|
}
|
|
}
|
|
|
|
TEST_P(UpsertExecutorTest, option_mergeObjects_false) {
|
|
std::string query = R"aql(
|
|
UPSERT {_key: "testee"}
|
|
INSERT {value: "invalid"}
|
|
)aql" + action() +
|
|
R"aql( {nestedObject: {foo: "bar"} }
|
|
INTO UnitTestCollection
|
|
OPTIONS {mergeObjects: false})aql";
|
|
AssertQueryHasResult(vocbase, query, VPackSlice::emptyArraySlice());
|
|
std::string testQuery =
|
|
R"aql(FOR x IN UnitTestCollection FILTER x._key == "testee" RETURN x.nestedObject)aql";
|
|
auto expected = VPackParser::fromJson(R"([{"foo": "bar"}])");
|
|
AssertQueryHasResult(vocbase, testQuery, expected->slice());
|
|
}
|
|
|
|
// TODO: In current implementaiton we search for exact match of _key, _rev which is not found.
|
|
// So we actually do insert, this needs to be fixed although this seems to be no production case
|
|
TEST_P(UpsertExecutorTest, DISABLED_option_ignoreRevs_default) {
|
|
std::string query = R"aql(
|
|
UPSERT {_key: "testee", _rev: "12345"}
|
|
INSERT {value: "invalid"}
|
|
)aql" + action() +
|
|
R"aql( {value: 2}
|
|
INTO UnitTestCollection)aql";
|
|
AssertQueryHasResult(vocbase, query, VPackSlice::emptyArraySlice());
|
|
|
|
auto expected = VPackParser::fromJson(R"([2])");
|
|
AssertQueryHasResult(vocbase, GetAllDocs, expected->slice());
|
|
}
|
|
|
|
// TODO: In current implementaiton we search for exact match of _key, _rev which is not found.
|
|
// So we actually do insert, this needs to be fixed although this seems to be no production case
|
|
TEST_P(UpsertExecutorTest, DISABLED_option_ignoreRevs_true) {
|
|
std::string query = R"aql(
|
|
UPSERT {_key: "testee", _rev: "12345"}
|
|
INSERT {value: "invalid"}
|
|
UPDATE {value: 2}
|
|
INTO UnitTestCollection
|
|
OPTIONS {ignoreRevs: true} )aql";
|
|
AssertQueryHasResult(vocbase, query, VPackSlice::emptyArraySlice());
|
|
|
|
auto expected = VPackParser::fromJson(R"([2])");
|
|
AssertQueryHasResult(vocbase, GetAllDocs, expected->slice());
|
|
}
|
|
// TODO: In current implementaiton we search for exact match of _key, _rev which is not found.
|
|
// So we actually do insert, this needs to be fixed although this seems to be no production case
|
|
TEST_P(UpsertExecutorTest, DISABLED_option_ignoreRevs_false) {
|
|
std::string query = R"aql(
|
|
UPSERT {_key: "testee", _rev: "12345"}
|
|
INSERT {value: "invalid"}
|
|
)aql" + action() +
|
|
R"aql( {value: 2}
|
|
INTO UnitTestCollection
|
|
OPTIONS {ignoreRevs: false} )aql";
|
|
AssertQueryFailsWith(vocbase, query, TRI_ERROR_ARANGO_CONFLICT);
|
|
AssertNotChanged();
|
|
}
|
|
|
|
TEST_P(UpsertExecutorTest, insert_not_found) {
|
|
std::string query = R"aql(
|
|
UPSERT {_key: "keyNotThere"}
|
|
INSERT {value: 2, sortValue: 2})aql" +
|
|
action() + R"aql({value: "invalid"}
|
|
INTO UnitTestCollection)aql";
|
|
AssertQueryHasResult(vocbase, query, VPackSlice::emptyArraySlice());
|
|
|
|
auto expected = VPackParser::fromJson(R"([1, 2])");
|
|
AssertQueryHasResult(vocbase, GetAllDocs, expected->slice());
|
|
}
|
|
|
|
TEST_P(UpsertExecutorTest, insert_on_value) {
|
|
std::string query = R"aql(
|
|
UPSERT {value: 2}
|
|
INSERT {value: 2, sortValue: 2})aql" +
|
|
action() + R"aql({value: "invalid"}
|
|
INTO UnitTestCollection)aql";
|
|
AssertQueryHasResult(vocbase, query, VPackSlice::emptyArraySlice());
|
|
|
|
auto expected = VPackParser::fromJson(R"([1, 2])");
|
|
AssertQueryHasResult(vocbase, GetAllDocs, expected->slice());
|
|
}
|
|
|
|
TEST_P(UpsertExecutorTest, upsert_on_value) {
|
|
std::string query = R"aql(
|
|
UPSERT {value: 1}
|
|
INSERT {value: "invalid"})aql" +
|
|
action() + R"aql({value: 2}
|
|
INTO UnitTestCollection)aql";
|
|
AssertQueryHasResult(vocbase, query, VPackSlice::emptyArraySlice());
|
|
|
|
auto expected = VPackParser::fromJson(R"([2])");
|
|
AssertQueryHasResult(vocbase, GetAllDocs, expected->slice());
|
|
}
|
|
|
|
TEST_P(UpsertExecutorTest, alternate_insert_update) {
|
|
std::string query = R"aql(
|
|
FOR i IN 20..30
|
|
UPSERT {value: 2}
|
|
INSERT {value: 2, sortValue: i})aql" +
|
|
action() + R"aql({value: i, sortValue: i}
|
|
INTO UnitTestCollection)aql";
|
|
AssertQueryHasResult(vocbase, query, VPackSlice::emptyArraySlice());
|
|
// The idea of this query is, that we first insert an unknown value 2.
|
|
// In the next iteration we are supposed to find this value 2.
|
|
// and overwrite it by something else.
|
|
// This will in turn allow to insert a new document with value 2.
|
|
auto expected = VPackParser::fromJson(R"([1, 21, 23, 25, 27, 29, 2])");
|
|
AssertQueryHasResult(vocbase, GetAllDocs, expected->slice());
|
|
}
|
|
|
|
INSTANTIATE_TEST_CASE_P(UpsertExecutorTestBasics, UpsertExecutorTest,
|
|
::testing::Values(UpsertType::UPDATE, UpsertType::REPLACE));
|
|
|
|
/*
|
|
* SECTION: Integration tests
|
|
*/
|
|
|
|
class UpsertExecutorIntegrationTest
|
|
: public testing::TestWithParam<std::tuple<UpsertType, size_t>> {
|
|
protected:
|
|
mocks::MockAqlServer server{};
|
|
TRI_vocbase_t& vocbase{server.getSystemDatabase()};
|
|
|
|
void SetUp() override {
|
|
SCOPED_TRACE("Setup");
|
|
auto info = VPackParser::fromJson(R"({"name":"UnitTestCollection"})");
|
|
auto collection = vocbase.createCollection(info->slice());
|
|
ASSERT_NE(collection.get(), nullptr) << "Failed to create collection";
|
|
// Insert Documents
|
|
std::string insertQuery =
|
|
R"aql(FOR i IN 1..)aql" + std::to_string(numDocs()) +
|
|
R"aql( INSERT {_key: TO_STRING(i), value: i, sortValue: i} INTO UnitTestCollection)aql";
|
|
SCOPED_TRACE(insertQuery);
|
|
AssertQueryHasResult(vocbase, insertQuery, VPackSlice::emptyArraySlice());
|
|
VPackBuilder expected;
|
|
{
|
|
VPackArrayBuilder a{&expected};
|
|
for (size_t i = 1; i <= numDocs(); ++i) {
|
|
expected.add(VPackValue(i));
|
|
}
|
|
}
|
|
AssertQueryHasResult(vocbase, GetAllDocs, expected.slice());
|
|
}
|
|
|
|
size_t numDocs() const {
|
|
auto const& [type, numDocs] = GetParam();
|
|
return numDocs;
|
|
}
|
|
|
|
UpsertType type() const {
|
|
auto const& [type, numDocs] = GetParam();
|
|
return type;
|
|
}
|
|
|
|
std::string const action() const {
|
|
if (type() == UpsertType::UPDATE) {
|
|
return "UPDATE";
|
|
}
|
|
return "REPLACE";
|
|
}
|
|
};
|
|
|
|
// This is disallowed in the parser (TRI_ERROR_QUERY_PARSE)
|
|
TEST_P(UpsertExecutorIntegrationTest, DISABLED_upsert_all) {
|
|
std::string query = R"aql(
|
|
FOR doc IN UnitTestCollection
|
|
UPSERT doc
|
|
INSERT {value: "invalid"}
|
|
)aql" + action() +
|
|
R"aql( {value: "foo"} IN UnitTestCollection)aql";
|
|
VPackBuilder expected;
|
|
{
|
|
VPackArrayBuilder a{&expected};
|
|
for (size_t i = 1; i <= numDocs(); ++i) {
|
|
expected.add(VPackValue("foo"));
|
|
}
|
|
}
|
|
AssertQueryHasResult(vocbase, query, VPackSlice::emptyArraySlice());
|
|
AssertQueryHasResult(vocbase, GetAllDocs, expected.slice());
|
|
}
|
|
|
|
TEST_P(UpsertExecutorIntegrationTest, upsert_all_by_key) {
|
|
std::string query = R"aql(FOR doc IN 1..)aql" + basics::StringUtils::itoa(numDocs()) +
|
|
R"aql( UPSERT {_key: TO_STRING(doc)}
|
|
INSERT {value: "invalid"} )aql" +
|
|
action() + R"aql( {value: 'foo'} IN UnitTestCollection)aql";
|
|
VPackBuilder expected;
|
|
{
|
|
VPackArrayBuilder a{&expected};
|
|
for (size_t i = 1; i <= numDocs(); ++i) {
|
|
expected.add(VPackValue("foo"));
|
|
}
|
|
}
|
|
AssertQueryHasResult(vocbase, query, VPackSlice::emptyArraySlice());
|
|
AssertQueryHasResult(vocbase, GetAllDocs, expected.slice());
|
|
}
|
|
|
|
TEST_P(UpsertExecutorIntegrationTest, upsert_all_by_id) {
|
|
std::string query =
|
|
R"aql(FOR doc IN 1..)aql" + basics::StringUtils::itoa(numDocs()) +
|
|
R"aql( UPSERT {_id: CONCAT("UnitTestCollection/", TO_STRING(doc)) }
|
|
INSERT {value: "invalid"} )aql" +
|
|
action() + R"aql( {value: 'foo'} IN UnitTestCollection)aql";
|
|
VPackBuilder expected;
|
|
{
|
|
VPackArrayBuilder a{&expected};
|
|
for (size_t i = 1; i <= numDocs(); ++i) {
|
|
expected.add(VPackValue("foo"));
|
|
}
|
|
}
|
|
AssertQueryHasResult(vocbase, query, VPackSlice::emptyArraySlice());
|
|
AssertQueryHasResult(vocbase, GetAllDocs, expected.slice());
|
|
}
|
|
|
|
TEST_P(UpsertExecutorIntegrationTest, upsert_only_even) {
|
|
std::string query = R"aql(
|
|
FOR sortValue IN 1..)aql" +
|
|
basics::StringUtils::itoa(numDocs()) +
|
|
R"aql(
|
|
FILTER sortValue % 2 == 0
|
|
UPSERT {sortValue}
|
|
INSERT {value: "invalid"} )aql" +
|
|
action() + R"aql(
|
|
{value: 'foo', sortValue} IN UnitTestCollection)aql";
|
|
VPackBuilder expected;
|
|
{
|
|
VPackArrayBuilder a{&expected};
|
|
for (size_t i = 1; i <= numDocs(); ++i) {
|
|
if (i % 2 == 0) {
|
|
expected.add(VPackValue("foo"));
|
|
} else {
|
|
expected.add(VPackValue(i));
|
|
}
|
|
}
|
|
}
|
|
AssertQueryHasResult(vocbase, query, VPackSlice::emptyArraySlice());
|
|
AssertQueryHasResult(vocbase, GetAllDocs, expected.slice());
|
|
}
|
|
|
|
TEST_P(UpsertExecutorIntegrationTest, upsert_all_but_skip) {
|
|
std::string query = R"aql(
|
|
FOR doc IN UnitTestCollection
|
|
SORT doc.sortValue
|
|
UPSERT {_key: doc._key}
|
|
INSERT {value: 'invalid'} )aql" +
|
|
action() + R"aql(
|
|
{value: 'foo', sortValue: doc.sortValue } IN UnitTestCollection
|
|
LIMIT 526, null
|
|
RETURN 1
|
|
)aql";
|
|
VPackBuilder expectedUpdateResponse;
|
|
VPackBuilder expected;
|
|
{
|
|
VPackArrayBuilder a{&expected};
|
|
VPackArrayBuilder b{&expectedUpdateResponse};
|
|
|
|
for (size_t i = 1; i <= numDocs(); ++i) {
|
|
expected.add(VPackValue("foo"));
|
|
if (i > 526) {
|
|
expectedUpdateResponse.add(VPackValue(1));
|
|
}
|
|
}
|
|
}
|
|
AssertQueryHasResult(vocbase, query, expectedUpdateResponse.slice());
|
|
AssertQueryHasResult(vocbase, GetAllDocs, expected.slice());
|
|
}
|
|
|
|
TEST_P(UpsertExecutorIntegrationTest, upsert_all_return_old) {
|
|
std::string query = R"aql(
|
|
FOR doc IN UnitTestCollection
|
|
SORT doc.sortValue
|
|
UPSERT {_key: doc._key}
|
|
INSERT {value: 'invalid'} )aql" +
|
|
action() + R"aql(
|
|
{value: 'foo', sortValue: doc.sortValue } IN UnitTestCollection
|
|
RETURN OLD.value
|
|
)aql";
|
|
VPackBuilder expectedUpdateResponse;
|
|
VPackBuilder expected;
|
|
{
|
|
VPackArrayBuilder a{&expected};
|
|
VPackArrayBuilder b{&expectedUpdateResponse};
|
|
|
|
for (size_t i = 1; i <= numDocs(); ++i) {
|
|
expected.add(VPackValue("foo"));
|
|
expectedUpdateResponse.add(VPackValue(i));
|
|
}
|
|
}
|
|
AssertQueryHasResult(vocbase, query, expectedUpdateResponse.slice());
|
|
AssertQueryHasResult(vocbase, GetAllDocs, expected.slice());
|
|
}
|
|
|
|
TEST_P(UpsertExecutorIntegrationTest, upsert_all_return_new) {
|
|
std::string query = R"aql(
|
|
FOR doc IN UnitTestCollection
|
|
SORT doc.sortValue
|
|
UPSERT {_key: doc._key}
|
|
INSERT {value: 'invalid'} )aql" +
|
|
action() + R"aql(
|
|
{value: 'foo', sortValue: doc.sortValue } IN UnitTestCollection
|
|
RETURN NEW.value
|
|
)aql";
|
|
|
|
VPackBuilder expected;
|
|
{
|
|
VPackArrayBuilder a{&expected};
|
|
|
|
for (size_t i = 1; i <= numDocs(); ++i) {
|
|
expected.add(VPackValue("foo"));
|
|
}
|
|
}
|
|
AssertQueryHasResult(vocbase, query, expected.slice());
|
|
AssertQueryHasResult(vocbase, GetAllDocs, expected.slice());
|
|
}
|
|
|
|
TEST_P(UpsertExecutorIntegrationTest, upsert_all_return_old_and_new) {
|
|
std::string query = R"aql(
|
|
FOR doc IN UnitTestCollection
|
|
SORT doc.sortValue
|
|
UPSERT {_key: doc._key}
|
|
INSERT {value: 'invalid'} )aql" +
|
|
action() + R"aql(
|
|
{value: 'foo', sortValue: doc.sortValue } IN UnitTestCollection
|
|
RETURN {old: OLD.value, new: NEW.value}
|
|
)aql";
|
|
|
|
VPackBuilder expectedUpdateResponse;
|
|
VPackBuilder expected;
|
|
{
|
|
VPackArrayBuilder a{&expected};
|
|
VPackArrayBuilder b{&expectedUpdateResponse};
|
|
|
|
for (size_t i = 1; i <= numDocs(); ++i) {
|
|
expected.add(VPackValue("foo"));
|
|
{
|
|
VPackObjectBuilder c{&expectedUpdateResponse};
|
|
expectedUpdateResponse.add("old", VPackValue(i));
|
|
expectedUpdateResponse.add("new", VPackValue("foo"));
|
|
}
|
|
}
|
|
}
|
|
AssertQueryHasResult(vocbase, query, expectedUpdateResponse.slice());
|
|
AssertQueryHasResult(vocbase, GetAllDocs, expected.slice());
|
|
}
|
|
|
|
TEST_P(UpsertExecutorIntegrationTest, upsert_handling_old_attributes) {
|
|
std::string query = R"aql(
|
|
FOR doc IN UnitTestCollection
|
|
UPSERT {_key: doc._key}
|
|
INSERT {value: "invalid"} )aql" +
|
|
action() + R"aql(
|
|
{foo: 'foo'} IN UnitTestCollection)aql";
|
|
VPackBuilder expected;
|
|
{
|
|
VPackArrayBuilder a{&expected};
|
|
if (type() == UpsertType::UPDATE) {
|
|
for (size_t i = 1; i <= numDocs(); ++i) {
|
|
expected.add(VPackValue(i));
|
|
}
|
|
} else {
|
|
for (size_t i = 1; i <= numDocs(); ++i) {
|
|
expected.add(VPackSlice::nullSlice());
|
|
}
|
|
}
|
|
}
|
|
AssertQueryHasResult(vocbase, query, VPackSlice::emptyArraySlice());
|
|
AssertQueryHasResult(vocbase, GetAllDocs, expected.slice());
|
|
}
|
|
|
|
TEST_P(UpsertExecutorIntegrationTest, upsert_in_subquery_multi_access) {
|
|
std::string query = R"aql(
|
|
FOR doc IN UnitTestCollection
|
|
SORT doc.sortValue
|
|
LET updated = (UPSERT {_key: doc._key}
|
|
INSERT {value: "invalid"} )aql" +
|
|
action() + R"aql(
|
|
{value: 'foo'} IN UnitTestCollection)
|
|
RETURN updated
|
|
)aql";
|
|
VPackBuilder expected;
|
|
{
|
|
VPackArrayBuilder a{&expected};
|
|
for (size_t i = 1; i <= numDocs(); ++i) {
|
|
expected.add(VPackValue(i));
|
|
}
|
|
}
|
|
AssertQueryFailsWith(vocbase, query, TRI_ERROR_QUERY_ACCESS_AFTER_MODIFICATION);
|
|
AssertQueryHasResult(vocbase, GetAllDocs, expected.slice());
|
|
}
|
|
|
|
TEST_P(UpsertExecutorIntegrationTest, upsert_in_subquery) {
|
|
std::string query = R"aql(
|
|
FOR x IN ["foo", "bar"]
|
|
FILTER x != "foo" /* The storage engine mock does NOT support multiple edits */
|
|
LET updated = (
|
|
FOR doc IN UnitTestCollection
|
|
UPSERT {_key: doc._key}
|
|
INSERT {value: "invalid"})aql" +
|
|
action() +
|
|
R"aql( {value: x} IN UnitTestCollection
|
|
)
|
|
RETURN updated
|
|
)aql";
|
|
// Both subqueries do not return anything
|
|
auto expectedUpdateResponse = VPackParser::fromJson(R"([[]])");
|
|
VPackBuilder expected;
|
|
{
|
|
VPackArrayBuilder a{&expected};
|
|
for (size_t i = 1; i <= numDocs(); ++i) {
|
|
expected.add(VPackValue("bar"));
|
|
}
|
|
}
|
|
AssertQueryHasResult(vocbase, query, expectedUpdateResponse->slice());
|
|
AssertQueryHasResult(vocbase, GetAllDocs, expected.slice());
|
|
}
|
|
|
|
TEST_P(UpsertExecutorIntegrationTest, upsert_in_subquery_with_outer_skip) {
|
|
std::string query = R"aql(
|
|
FOR x IN 1..2
|
|
LET updated = (
|
|
FILTER x < 2
|
|
FOR doc IN UnitTestCollection
|
|
UPSERT {_key: doc._key}
|
|
INSERT {value: "invalid"})aql" +
|
|
action() +
|
|
R"aql( {value: "foo"} IN UnitTestCollection)
|
|
LIMIT 1, null
|
|
RETURN updated
|
|
)aql";
|
|
VPackBuilder expectedUpdateResponse;
|
|
VPackBuilder expected;
|
|
{
|
|
// [ [] ]
|
|
VPackArrayBuilder b{&expectedUpdateResponse};
|
|
VPackArrayBuilder c{&expectedUpdateResponse};
|
|
|
|
VPackArrayBuilder a{&expected};
|
|
for (size_t i = 1; i <= numDocs(); ++i) {
|
|
expected.add(VPackValue("foo"));
|
|
}
|
|
}
|
|
AssertQueryHasResult(vocbase, query, expectedUpdateResponse.slice());
|
|
AssertQueryHasResult(vocbase, GetAllDocs, expected.slice());
|
|
}
|
|
|
|
TEST_P(UpsertExecutorIntegrationTest, upsert_in_subquery_with_inner_skip) {
|
|
std::string query = R"aql(
|
|
FOR x IN 1..2
|
|
LET updated = (
|
|
FILTER x < 2
|
|
FOR doc IN UnitTestCollection
|
|
UPSERT {_key: doc._key}
|
|
INSERT {value: "invalid"})aql" +
|
|
action() +
|
|
R"aql( {value: CONCAT('foo', TO_STRING(x))} IN UnitTestCollection
|
|
LIMIT 526, null
|
|
RETURN 1
|
|
)
|
|
RETURN LENGTH(updated)
|
|
)aql";
|
|
|
|
VPackBuilder expectedUpdateResponse;
|
|
{
|
|
VPackArrayBuilder a{&expectedUpdateResponse};
|
|
if (numDocs() < 526) {
|
|
expectedUpdateResponse.add(VPackValue(0));
|
|
} else {
|
|
expectedUpdateResponse.add(VPackValue(numDocs() - 526));
|
|
}
|
|
// Second subquery is fully filtered
|
|
expectedUpdateResponse.add(VPackValue(0));
|
|
}
|
|
|
|
VPackBuilder expected;
|
|
{
|
|
VPackArrayBuilder a{&expected};
|
|
for (size_t i = 1; i <= numDocs(); ++i) {
|
|
expected.add(VPackValue("foo1"));
|
|
}
|
|
}
|
|
AssertQueryHasResult(vocbase, query, expectedUpdateResponse.slice());
|
|
AssertQueryHasResult(vocbase, GetAllDocs, expected.slice());
|
|
}
|
|
|
|
TEST_P(UpsertExecutorIntegrationTest, upsert_all_insert_by_key) {
|
|
std::string from = basics::StringUtils::itoa(numDocs() + 1);
|
|
std::string to = basics::StringUtils::itoa(numDocs() + numDocs());
|
|
// The keys are out-of-range for the existing ones, so we trigger INSERT case
|
|
std::string query = R"aql(FOR doc IN )aql" + from + R"aql(..)aql" + to +
|
|
R"aql( UPSERT {_key: TO_STRING(doc)}
|
|
INSERT {_key: TO_STRING(doc), value: "foo", sortValue: doc} )aql" +
|
|
action() + R"aql( {value: 'invalid'} IN UnitTestCollection)aql";
|
|
VPackBuilder expected;
|
|
{
|
|
VPackArrayBuilder a{&expected};
|
|
// Keep first untouched
|
|
for (size_t i = 1; i <= numDocs(); ++i) {
|
|
expected.add(VPackValue(i));
|
|
}
|
|
// INSERT as many as we already have with foo
|
|
for (size_t i = 1; i <= numDocs(); ++i) {
|
|
expected.add(VPackValue("foo"));
|
|
}
|
|
}
|
|
AssertQueryHasResult(vocbase, query, VPackSlice::emptyArraySlice());
|
|
AssertQueryHasResult(vocbase, GetAllDocs, expected.slice());
|
|
}
|
|
|
|
TEST_P(UpsertExecutorIntegrationTest, upsert_first_update_then_insert) {
|
|
std::string from = basics::StringUtils::itoa(1);
|
|
std::string to = basics::StringUtils::itoa(numDocs() + numDocs());
|
|
// We start with updates, then hit end of keyrange and insert
|
|
std::string query = R"aql(FOR doc IN )aql" + from + R"aql(..)aql" + to +
|
|
R"aql( UPSERT {_key: TO_STRING(doc)}
|
|
INSERT {_key: TO_STRING(doc), value: "foo", sortValue: doc} )aql" +
|
|
action() + R"aql( {value: 'bar'} IN UnitTestCollection)aql";
|
|
VPackBuilder expected;
|
|
{
|
|
VPackArrayBuilder a{&expected};
|
|
// Updates result in BAR
|
|
for (size_t i = 1; i <= numDocs(); ++i) {
|
|
expected.add(VPackValue("bar"));
|
|
}
|
|
// INSERT as many as we already have with foo
|
|
for (size_t i = 1; i <= numDocs(); ++i) {
|
|
expected.add(VPackValue("foo"));
|
|
}
|
|
}
|
|
AssertQueryHasResult(vocbase, query, VPackSlice::emptyArraySlice());
|
|
AssertQueryHasResult(vocbase, GetAllDocs, expected.slice());
|
|
}
|
|
|
|
TEST_P(UpsertExecutorIntegrationTest, upsert_first_insert_then_update) {
|
|
std::string from = basics::StringUtils::itoa(numDocs() + numDocs());
|
|
std::string to = basics::StringUtils::itoa(1);
|
|
// We start with insert, then hit end of keyrange and start with updates.
|
|
// We iterate downwards
|
|
std::string query = R"aql(FOR doc IN )aql" + from + R"aql(..)aql" + to +
|
|
R"aql( UPSERT {_key: TO_STRING(doc)}
|
|
INSERT {_key: TO_STRING(doc), value: "foo", sortValue: doc} )aql" +
|
|
action() + R"aql( {value: 'bar'} IN UnitTestCollection)aql";
|
|
VPackBuilder expected;
|
|
{
|
|
VPackArrayBuilder a{&expected};
|
|
// Updates result in BAR
|
|
for (size_t i = 1; i <= numDocs(); ++i) {
|
|
expected.add(VPackValue("bar"));
|
|
}
|
|
// INSERT as many as we already have with foo
|
|
for (size_t i = 1; i <= numDocs(); ++i) {
|
|
expected.add(VPackValue("foo"));
|
|
}
|
|
}
|
|
AssertQueryHasResult(vocbase, query, VPackSlice::emptyArraySlice());
|
|
AssertQueryHasResult(vocbase, GetAllDocs, expected.slice());
|
|
}
|
|
|
|
TEST_P(UpsertExecutorIntegrationTest, upsert_alternate_insert_upsert) {
|
|
std::string from = basics::StringUtils::itoa(1);
|
|
std::string to = basics::StringUtils::itoa(numDocs() + numDocs());
|
|
// We alternate between inserts and updates
|
|
// If number is disible by two, we divide it by two. (in key range = update)
|
|
// If not, we divide, round floor and add 1000 (out of key range = insert)
|
|
std::string query = R"aql(FOR preMod IN )aql" + from + R"aql(..)aql" + to +
|
|
R"aql(
|
|
LET doc = (preMod % 2 == 0) ? (preMod / 2) : (floor(preMod / 2) + 2000)
|
|
UPSERT {_key: TO_STRING(doc)}
|
|
INSERT {_key: TO_STRING(doc), value: "foo", sortValue: preMod} )aql" +
|
|
action() + R"aql( {value: 'bar', sortValue: preMod} IN UnitTestCollection)aql";
|
|
VPackBuilder expected;
|
|
{
|
|
VPackArrayBuilder a{&expected};
|
|
// Updates result in BAR
|
|
for (size_t i = 1; i <= numDocs() * 2; ++i) {
|
|
if (i % 2 == 0) {
|
|
expected.add(VPackValue("bar"));
|
|
} else {
|
|
expected.add(VPackValue("foo"));
|
|
}
|
|
}
|
|
}
|
|
AssertQueryHasResult(vocbase, query, VPackSlice::emptyArraySlice());
|
|
AssertQueryHasResult(vocbase, GetAllDocs, expected.slice());
|
|
}
|
|
|
|
INSTANTIATE_TEST_CASE_P(UpsertExecutorIntegration, UpsertExecutorIntegrationTest,
|
|
::testing::Combine(::testing::Values(UpsertType::UPDATE, UpsertType::REPLACE),
|
|
::testing::Values(1, 1001)));
|
|
|
|
/* This works as well, but takes considerably more time to pass.
|
|
INSTANTIATE_TEST_CASE_P(UpsertExecutorIntegration, UpsertExecutorIntegrationTest,
|
|
::testing::Combine(::testing::Values(UpsertType::UPDATE, UpsertType::REPLACE),
|
|
::testing::Values(1, 999, 1000, 1001, 2001)));
|
|
*/
|
|
|
|
} // namespace aql
|
|
} // namespace tests
|
|
} // namespace arangodb
|