1
0
Fork 0

Merge branch 'devel' of github.com:arangodb/ArangoDB into feature/upgrade-v8

This commit is contained in:
Wilfried Goesgens 2019-11-18 16:12:54 +01:00
commit e06191d8ad
9 changed files with 488 additions and 330 deletions

View File

@ -3233,12 +3233,6 @@ struct SortToIndexNode final : public WalkerWorker<ExecutionNode> {
// sorted GatherNode in the cluster
indexNode->needsGatherNodeSort(true);
_modified = true;
} else if (numCovered > 0 && sortCondition.isUnidirectional()) {
// remove the first few attributes if they are constant
SortNode* sortNode =
ExecutionNode::castTo<SortNode*>(_plan->getNodeById(_sortNode->id()));
sortNode->removeConditions(numCovered);
_modified = true;
}
}
}

View File

@ -1,157 +0,0 @@
////////////////////////////////////////////////////////////////////////////////
/// DISCLAIMER
///
/// Copyright 2018 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 Jan Christoph Uhde
////////////////////////////////////////////////////////////////////////////////
#ifndef ARANGOD_AQL_SINGLE_BLOCK_FETCHER_H
#define ARANGOD_AQL_SINGLE_BLOCK_FETCHER_H
#include "Aql/AqlItemBlock.h"
#include "Aql/AqlItemMatrix.h"
#include "Aql/DependencyProxy.h"
#include "Aql/ExecutionState.h"
#include "Aql/InputAqlItemRow.h"
#include "Aql/SortExecutor.h"
#include <Basics/Exceptions.h>
#include <memory>
namespace arangodb {
namespace aql {
class AqlItemBlock;
template <BlockPassthrough>
class DependencyProxy;
/**
* @brief Interface for all AqlExecutors that do need all
* rows at a time in order to make progress.
*/
template <BlockPassthrough pass>
class SingleBlockFetcher {
public:
explicit SingleBlockFetcher(DependencyProxy<pass>& executionBlock)
: _prefetched(false),
_dependencyProxy(&executionBlock),
_currentBlock(nullptr),
_upstreamState(ExecutionState::HASMORE) {}
TEST_VIRTUAL ~SingleBlockFetcher() = default;
protected:
// only for testing! Does not initialize _dependencyProxy!
SingleBlockFetcher() = default;
public:
/**
* @brief Fetch one new AqlItemRow from upstream.
* **Guarantee**: the pointer returned is valid only
* until the next call to fetchRow.
*
* @return A pair with the following properties:
* ExecutionState:
* WAITING => IO going on, immediatly return to caller.
* DONE => No more to expect from Upstream, if you are done with
* this row return DONE to caller.
* HASMORE => There is potentially more from above, call again if
* you need more input.
* AqlItemRow:
* If WAITING => Do not use this Row, it is a nullptr.
* If HASMORE => The Row is guaranteed to not be a nullptr.
* If DONE => Row can be a nullptr (nothing received) or valid.
*/
// SingleBlockFetcher cannot pass through. Could be implemented, but currently
// there are no executors that could use this and not better use
// SingleRowFetcher instead.
std::pair<ExecutionState, SharedAqlItemBlockPtr> fetchBlock(
std::size_t limit = ExecutionBlock::DefaultBatchSize(), bool prefetch = false) {
if (_prefetched) {
TRI_ASSERT(!prefetch);
_prefetched = false;
return {_upstreamState, _currentBlock};
}
if (_upstreamState == ExecutionState::DONE) {
TRI_ASSERT(_currentBlock == nullptr);
return {_upstreamState, _currentBlock};
}
auto res = _dependencyProxy->fetchBlock(limit);
_upstreamState = res.first;
_currentBlock = res.second;
if (prefetch && _currentBlock != nullptr) {
_prefetched = prefetch;
}
return res;
}
std::pair<ExecutionState, SharedAqlItemBlockPtr> fetchBlockForModificationExecutor(
std::size_t limit = ExecutionBlock::DefaultBatchSize()) {
return fetchBlock(limit);
}
std::pair<ExecutionState, SharedAqlItemBlockPtr> fetchBlockForPassthrough(size_t) {
THROW_ARANGO_EXCEPTION(TRI_ERROR_NOT_IMPLEMENTED);
};
std::pair<ExecutionState, std::size_t> preFetchNumberOfRows(std::size_t) {
fetchBlock(true);
return {_upstreamState, _currentBlock != nullptr ? _currentBlock->size() : 0};
}
InputAqlItemRow accessRow(std::size_t index) {
TRI_ASSERT(_currentBlock != nullptr);
TRI_ASSERT(index < _currentBlock->size());
return InputAqlItemRow{_currentBlock, index};
}
ExecutionState upstreamState() const { return _upstreamState; }
SharedAqlItemBlockPtr currentBlock() const { return _currentBlock; }
// on purpose not implemented, this Fetcher is about to be removed
// NOLINTNEXTLINE google-default-arguments
std::pair<ExecutionState, ShadowAqlItemRow> fetchShadowRow(size_t atMost = 1) const {
THROW_ARANGO_EXCEPTION(TRI_ERROR_NOT_IMPLEMENTED);
}
bool _prefetched;
private:
DependencyProxy<pass>* _dependencyProxy;
SharedAqlItemBlockPtr _currentBlock;
ExecutionState _upstreamState;
private:
/**
* @brief Delegates to ExecutionBlock::getNrInputRegisters()
*/
RegisterId getNrInputRegisters() const {
return _dependencyProxy->getNrInputRegisters();
}
};
} // namespace aql
} // namespace arangodb
#endif // ARANGOD_AQL_SINGLE_BLOCK_FETCHER_H

View File

@ -189,25 +189,21 @@ size_t SortCondition::coveredAttributes(
}
// no match
bool isConstant = false;
if (isContained(indexAttributes, field.attributes) &&
isContained(_constAttributes, field.attributes)) {
// no field match, but a constant attribute
isConstant = true;
++fieldsPosition;
++numCovered;
continue;
}
if (!isConstant && isContained(_constAttributes, indexAttributes[i])) {
if (isContained(_constAttributes, indexAttributes[i])) {
// no field match, but a constant attribute
isConstant = true;
++i; // next index field
continue;
}
if (!isConstant) {
break;
}
break;
}
TRI_ASSERT(numCovered <= _fields.size());

View File

@ -1908,9 +1908,88 @@ arangodb::Result fromFuncMinMatch(irs::boolean_filter* filter, QueryContext cons
return {};
}
arangodb::Result processPhraseArgs(
irs::by_phrase* phrase, QueryContext const& ctx,
FilterContext const& filterCtx, arangodb::aql::AstNode const& valueArgs,
size_t valueArgsBegin, size_t valueArgsEnd, irs::analysis::analyzer::ptr& analyzer,
size_t offset, bool allowDefaultOffset, bool allowRecursion) {
irs::string_ref value;
bool expectingOffset = false;
for (size_t idx = valueArgsBegin; idx < valueArgsEnd; ++idx) {
auto currentArg = valueArgs.getMemberUnchecked(idx);
if (!currentArg) {
auto message = "'PHRASE' AQL function: Unable to parse argument on position "s + std::to_string(idx);
LOG_TOPIC("44bed", WARN, arangodb::iresearch::TOPIC) << message;
return { TRI_ERROR_BAD_PARAMETER, message };
}
if (currentArg->isArray() && (!expectingOffset || allowDefaultOffset)) {
// array arg is processed with possible default 0 offsets - to be easily compatible with TOKENS function
// No array recursion allowed. This could be allowed, but just looks tangled.
// Anyone interested coud use FLATTEN to explicitly require processing all recurring arrays as one array
if (allowRecursion) {
auto subRes = processPhraseArgs(phrase, ctx, filterCtx, *currentArg, 0, currentArg->numMembers(), analyzer, offset, true, false);
if (subRes.fail()) {
return subRes;
}
expectingOffset = true;
offset = 0;
continue;
} else {
auto message = "'PHRASE' AQL function: recursive arrays not allowed at position "s + std::to_string(idx);
LOG_TOPIC("66c24", WARN, arangodb::iresearch::TOPIC) << message;
return { TRI_ERROR_BAD_PARAMETER, message };
}
}
ScopedAqlValue currentValue(*currentArg);
if (phrase || currentValue.isConstant()) {
if (!currentValue.execute(ctx)) {
auto message = "'PHRASE' AQL function: Unable to parse argument on position " + std::to_string(idx);
LOG_TOPIC("d819d", WARN, arangodb::iresearch::TOPIC) << message;
return { TRI_ERROR_BAD_PARAMETER, message };
}
if (arangodb::iresearch::SCOPED_VALUE_TYPE_DOUBLE == currentValue.type() && expectingOffset) {
offset = static_cast<uint64_t>(currentValue.getInt64());
expectingOffset = false;
continue; // got offset let`s go search for value
} else if ( (arangodb::iresearch::SCOPED_VALUE_TYPE_STRING != currentValue.type() || !currentValue.getString(value)) || // value is not a string at all
(expectingOffset && !allowDefaultOffset)) { // offset is expected mandatory but got value
std::string expectedValue;
if (expectingOffset && allowDefaultOffset) {
expectedValue = " as a value or offset";
} else if (expectingOffset) {
expectedValue = " as an offset";
} else {
expectedValue = " as a value";
}
auto message = "'PHRASE' AQL function: Unable to parse argument on position " + std::to_string(idx) + expectedValue;
LOG_TOPIC("ac06b", WARN, arangodb::iresearch::TOPIC) << message;
return { TRI_ERROR_BAD_PARAMETER, message };
}
} else {
// in case of non const node encountered while parsing we can not decide if current and following args are correct before execution
// so at this stage we say all is ok
return {};
}
if (phrase) {
TRI_ASSERT(analyzer);
appendTerms(*phrase, value, *analyzer, offset);
}
offset = 0;
expectingOffset = true;
}
if (!expectingOffset) { // that means last arg is numeric - this is error as no term to apply offset to
auto message = "'PHRASE' AQL function : Unable to parse argument on position " + std::to_string(valueArgsEnd - 1) + "as a value"s;
LOG_TOPIC("5fafe", WARN, arangodb::iresearch::TOPIC) << message;
return { TRI_ERROR_BAD_PARAMETER, message };
}
return {};
}
// note: <value> could be either string ether array of strings with offsets inbetween . Inside array
// 0 offset could be omitted e.g. [term1, term2, 2, term3] is equal to: [term1, 0, term2, 2, term3]
// PHRASE(<attribute>, <value> [, <offset>, <value>, ...] [, <analyzer>])
// PHRASE(<attribute>, '[' <value> [, <offset>, <value>, ...] ']' [,
// <analyzer>])
// PHRASE(<attribute>, '[' <value> [, <offset>, <value>, ...] ']' [,<analyzer>])
arangodb::Result fromFuncPhrase(irs::boolean_filter* filter, QueryContext const& ctx,
FilterContext const& filterCtx, arangodb::aql::AstNode const& args) {
if (!args.isDeterministic()) {
@ -1940,7 +2019,7 @@ arangodb::Result fromFuncPhrase(irs::boolean_filter* filter, QueryContext const&
ctx, argc, "PHRASE");
if (!analyzerPool._pool) {
return {TRI_ERROR_INTERNAL};
return {TRI_ERROR_BAD_PARAMETER};
}
}
@ -1958,70 +2037,15 @@ arangodb::Result fromFuncPhrase(irs::boolean_filter* filter, QueryContext const&
}
// ...........................................................................
// 2nd argument defines a value
// 2nd argument and later defines a values
// ...........................................................................
auto const* valueArg = args.getMemberUnchecked(1);
if (!valueArg) {
auto message = "'PHRASE' AQL function: 2nd argument is invalid";
LOG_TOPIC("c3aec", WARN, arangodb::iresearch::TOPIC) << message;
return {TRI_ERROR_BAD_PARAMETER, message};
}
auto* valueArgs = &args;
size_t valueArgsBegin = 1;
size_t valueArgsEnd = argc;
if (valueArg->isArray()) {
valueArgs = valueArg;
valueArgsBegin = 0;
valueArgsEnd = valueArg->numMembers();
if (0 == (valueArgsEnd & 1)) {
auto message = "'PHRASE' AQL function: 2nd argument has an invalid number of members (must be an odd number)";
LOG_TOPIC("05c0c", WARN, arangodb::iresearch::TOPIC) << message;
return {TRI_ERROR_BAD_PARAMETER, message};
}
valueArg = valueArgs->getMemberUnchecked(valueArgsBegin);
if (!valueArg) {
std::stringstream ss;;
ss << valueArg;
auto message = "'PHRASE' AQL function: 2nd argument has an invalid member at offset: "s + ss.str();
LOG_TOPIC("892bc", WARN, arangodb::iresearch::TOPIC) << message;
return {TRI_ERROR_BAD_PARAMETER, message};
}
}
irs::string_ref value;
ScopedAqlValue inputValue(*valueArg);
if (filter || inputValue.isConstant()) {
if (!inputValue.execute(ctx)) {
auto message = "'PHRASE' AQL function: Failed to evaluate 2nd argument";
LOG_TOPIC("14a81", WARN, arangodb::iresearch::TOPIC) << message;
return {TRI_ERROR_BAD_PARAMETER, message};
}
if (arangodb::iresearch::SCOPED_VALUE_TYPE_STRING != inputValue.type()) {
auto message = "'PHRASE' AQL function: 2nd argument has invalid type '"s +
ScopedAqlValue::typeString(inputValue.type()).c_str() + "' (string expected)";
LOG_TOPIC("a91b6", WARN, arangodb::iresearch::TOPIC) << message;
return {TRI_ERROR_BAD_PARAMETER, message};
}
if (!inputValue.getString(value)) {
auto message = "'PHRASE' AQL function: Unable to parse 2nd argument as string";
LOG_TOPIC("b546d", WARN, arangodb::iresearch::TOPIC) << message;
return {TRI_ERROR_BAD_PARAMETER, message};
}
}
irs::by_phrase* phrase = nullptr;
irs::analysis::analyzer::ptr analyzer;
// prepare filter if execution phase
if (filter) {
std::string name;
@ -2032,7 +2056,7 @@ arangodb::Result fromFuncPhrase(irs::boolean_filter* filter, QueryContext const&
}
TRI_ASSERT(analyzerPool._pool);
analyzer = analyzerPool._pool->get(); // get analyzer from pool
analyzer = analyzerPool._pool->get();
if (!analyzer) {
auto message = "'PHRASE' AQL function: Unable to instantiate analyzer '"s + analyzerPool._pool->name() + "'";
@ -2045,63 +2069,10 @@ arangodb::Result fromFuncPhrase(irs::boolean_filter* filter, QueryContext const&
phrase = &filter->add<irs::by_phrase>();
phrase->field(std::move(name));
phrase->boost(filterCtx.boost);
TRI_ASSERT(analyzer);
appendTerms(*phrase, value, *analyzer, 0);
}
decltype(fieldArg) offsetArg = nullptr;
size_t offset = 0;
for (size_t idx = valueArgsBegin + 1, end = valueArgsEnd; idx < end; idx += 2) {
offsetArg = valueArgs->getMemberUnchecked(idx);
if (!offsetArg) {
auto message = "'PHRASE' AQL function: Unable to parse argument on position "s + std::to_string(idx) + " as an offset"s;
LOG_TOPIC("44bed", WARN, arangodb::iresearch::TOPIC) << message;
return {TRI_ERROR_BAD_PARAMETER, message};
}
valueArg = valueArgs->getMemberUnchecked(idx + 1);
if (!valueArg) {
auto message = "'PHRASE' AQL function: Unable to parse argument on position " + std::to_string(idx + 1) + " as a value";
LOG_TOPIC("ac06b", WARN, arangodb::iresearch::TOPIC) << message;
return {TRI_ERROR_BAD_PARAMETER, message};
}
ScopedAqlValue offsetValue(*offsetArg);
if (filter || offsetValue.isConstant()) {
if (!offsetValue.execute(ctx) ||
arangodb::iresearch::SCOPED_VALUE_TYPE_DOUBLE != offsetValue.type()) {
auto message = "'PHRASE' AQL function: Unable to parse argument on position " + std::to_string(idx) + " as an offset";
LOG_TOPIC("d819d", WARN, arangodb::iresearch::TOPIC) << message;
return {TRI_ERROR_BAD_PARAMETER, message};
}
offset = static_cast<uint64_t>(offsetValue.getInt64());
}
ScopedAqlValue inputValue(*valueArg);
if (filter || inputValue.isConstant()) {
if (!inputValue.execute(ctx) ||
arangodb::iresearch::SCOPED_VALUE_TYPE_STRING != inputValue.type() ||
!inputValue.getString(value)) {
auto message = "'PHRASE' AQL function: Unable to parse argument on position " + std::to_string(idx + 1) + " as a value";
LOG_TOPIC("39e12", WARN, arangodb::iresearch::TOPIC) << message;
return {TRI_ERROR_BAD_PARAMETER, message};
}
}
if (phrase) {
TRI_ASSERT(analyzer);
appendTerms(*phrase, value, *analyzer, offset);
}
}
return { }; //ok;
// on top level we require explicit offsets - to be backward compatible and be able to distinguish last argument as analyzer or value
// Also we allow recursion inside array to support older syntax (one array arg) and add ability to pass several arrays as args
return processPhraseArgs(phrase, ctx, filterCtx, *valueArgs, valueArgsBegin, valueArgsEnd, analyzer, 0, false, true);
}
// STARTS_WITH(<attribute>, <prefix>, [<scoring-limit>])

View File

@ -2609,10 +2609,6 @@ TEST_F(IResearchFilterFunctionTest, Phrase) {
"FOR d IN myView FILTER AnalYZER(phrase(d.name, 'quick', d.name, "
"'brown'), 'test_analyzer') RETURN d",
&ExpressionContextMock::EMPTY);
assertFilterFail(
vocbase(),
"FOR d IN myView FILTER AnaLYZER(phrase(d.name, [ 'quick', '0', "
"'brown' ]), 'test_analyzer') RETURN d");
assertFilterFail(
vocbase(),
"FOR d IN myView FILTER AnaLYZER(phrase(d.name, [ 'quick', null, "
@ -2632,6 +2628,24 @@ TEST_F(IResearchFilterFunctionTest, Phrase) {
&ExpressionContextMock::EMPTY);
}
{
irs::Or expected;
auto& phrase = expected.add<irs::by_phrase>();
phrase.field(mangleString("name", "test_analyzer"));
phrase.push_back("q").push_back("u").push_back("i").push_back("c").push_back(
"k").push_back("0");
phrase.push_back("b").push_back("r").push_back("o").push_back("w").push_back(
"n");
assertFilterSuccess(
vocbase(),
"FOR d IN myView FILTER AnaLYZER(phrase(d.name, [ 'quick', '0', "
"'brown' ]), 'test_analyzer') RETURN d", expected);
assertFilterFail(
vocbase(),
"FOR d IN myView FILTER AnaLYZER(phrase(d.name, 'quick', '0', "
"'brown'), 'test_analyzer') RETURN d");
}
// with offset, complex name, custom analyzer
// quick <...> <...> <...> <...> <...> brown
{
@ -3373,11 +3387,6 @@ TEST_F(IResearchFilterFunctionTest, Phrase) {
"FOR d IN myView FILTER analyzer(phrase(d.obj.properties.id.name, "
"'quick', 3, 'brown', false, 'fox', 0, 'jumps'), 'test_analyzer') "
"RETURN d");
assertFilterFail(
vocbase(),
"FOR d IN myView FILTER analyzer(phrase(d.obj.properties.id.name, [ "
"'quick', 3, 'brown', '2', 'fox', 0, 'jumps' ]), 'test_analyzer') "
"RETURN d");
assertFilterFail(
vocbase(),
"FOR d IN myView FILTER analyzer(phrase(d.obj.properties.id.name, [ "
@ -3561,6 +3570,55 @@ TEST_F(IResearchFilterFunctionTest, Phrase) {
"'fox', 0.0, 'jumps' ], 'test_analyzer') RETURN d",
expected, &ctx);
}
{
irs::Or expected;
auto& phrase = expected.add<irs::by_phrase>();
phrase.field(mangleString("obj.properties.id.name", "test_analyzer"));
phrase.push_back("q").push_back("u").push_back("i").push_back("c").push_back(
"k");
phrase.push_back("b", 3).push_back("r").push_back("o").push_back("w").push_back(
"n");
phrase.push_back("f").push_back("o").push_back("x");
phrase.push_back("j").push_back("u").push_back("m").push_back("p").push_back(
"s");
ExpressionContextMock ctx;
ctx.vars.emplace("offset", arangodb::aql::AqlValue(arangodb::aql::AqlValueHintInt(2)));
ctx.vars.emplace("input", arangodb::aql::AqlValue("bro"));
// implicit zero offsets
assertFilterSuccess(
vocbase(),
"LET offset=2 LET input='bro' FOR d IN myView FILTER "
"analyzer(phrase(d.obj.properties.id.name, ['quick', offset+1, "
"CONCAT(input, 'wn'), 'fox', 'jumps']), 'test_analyzer') "
"RETURN d",
expected, &ctx);
// explicit zero offsets on top level
assertFilterSuccess(
vocbase(),
"LET offset=2 LET input='bro' FOR d IN myView FILTER "
"analyzer(phrase(d.obj.properties.id.name, ['quick'], offset+1, "
"CONCAT(input, 'wn'), 0, ['f', 'o', 'x'], 0, ['j', 'u', 'mps']), 'test_analyzer') "
"RETURN d",
expected, &ctx);
// recurring arrays not allowed
assertFilterFail(
vocbase(),
"FOR d IN myView FILTER "
"analyzer(phrase(d.obj.properties.id.name, ['quick'], 3, "
"'123', 'wn', 0, ['f', 'o', 'x'], 0, ['j', ['u'], 'mps']), 'test_analyzer') "
"RETURN d", &ctx);
assertFilterFail(
vocbase(),
"FOR d IN myView FILTER "
"analyzer(phrase(d.obj.properties.id.name, ['quick', 3, "
"'123', 'wn', 0, 'f', 'o', 'x', 0, ['j'], 'u', 'mps']), 'test_analyzer') "
"RETURN d", &ctx);
}
// multiple offsets, complex name, custom analyzer, invalid expressions
// quick <...> <...> <...> brown <...> <...> fox jumps
@ -3601,13 +3659,6 @@ TEST_F(IResearchFilterFunctionTest, Phrase) {
"TO_BOOL(offset+1), CONCAT(input, 'wn'), offset, 'fox', 0, 'jumps' ]), "
"'test_analyzer') RETURN d",
&ctx);
assertFilterExecutionFail(
vocbase(),
"LET offset=2 LET input='bro' FOR d IN myView FILTER "
"analyzer(phrase(d.obj.properties.id.name, [ 'quick', offset + 1.5, "
"'brown', TO_STRING(2), 'fox', 0, 'jumps' ]), 'test_analyzer') RETURN "
"d",
&ctx);
assertFilterExecutionFail(
vocbase(),
"LET offset=2 LET input='bro' FOR d IN myView FILTER "
@ -3652,12 +3703,6 @@ TEST_F(IResearchFilterFunctionTest, Phrase) {
"CONCAT(input, 'wn'), offset, 'fox', 0, 'jumps' ], 'test_analyzer') "
"RETURN d",
&ctx);
assertFilterExecutionFail(
vocbase(),
"LET offset=2 LET input='bro' FOR d IN myView FILTER "
"phrase(d.obj.properties.id.name, [ 'quick', offset + 1.5, 'brown', "
"TO_STRING(2), 'fox', 0, 'jumps' ], 'test_analyzer') RETURN d",
&ctx);
assertFilterExecutionFail(
vocbase(),
"LET offset=2 LET input='bro' FOR d IN myView FILTER "
@ -3713,9 +3758,10 @@ TEST_F(IResearchFilterFunctionTest, Phrase) {
vocbase(),
"FOR d IN myView FILTER analyzer(phrase(d['name'], 'quick'), { \"a\": 7, "
"\"b\": \"c\" }) RETURN d");
assertFilterFail(vocbase(),
"FOR d IN myView FILTER analyzer(phrase(d.name, 'quick'), "
"'invalid_analyzer') RETURN d");
assertFilterFail(
vocbase(),
"FOR d IN myView FILTER analyzer(phrase(d.name, 'quick'), "
"'invalid_analyzer') RETURN d");
assertFilterFail(
vocbase(),
"FOR d IN myView FILTER analyzer(phrase(d['name'], 'quick'), "
@ -3848,9 +3894,10 @@ TEST_F(IResearchFilterFunctionTest, Phrase) {
vocbase(),
"FOR d IN myView FILTER phrase(d.name, [ 'quick' ], 'invalid_analyzer') "
"RETURN d");
assertFilterFail(vocbase(),
"FOR d IN myView FILTER phrase(d['name'], [ 'quick' ], "
"'invalid_analyzer') RETURN d");
assertFilterFail(
vocbase(),
"FOR d IN myView FILTER phrase(d['name'], [ 'quick' ], "
"'invalid_analyzer') RETURN d");
// wrong analylzer
assertFilterFail(
@ -3883,9 +3930,10 @@ TEST_F(IResearchFilterFunctionTest, Phrase) {
vocbase(),
"FOR d IN myView FILTER analyzer(phrase(d.name, 'quick'), null) RETURN "
"d");
assertFilterFail(vocbase(),
"FOR d IN myView FILTER analyzer(phrase(d.name, 'quick'), "
"'invalidAnalyzer') RETURN d");
assertFilterFail(
vocbase(),
"FOR d IN myView FILTER analyzer(phrase(d.name, 'quick'), "
"'invalidAnalyzer') RETURN d");
assertFilterExecutionFail(
vocbase(),
"FOR d IN myView FILTER analyzer(phrase(d.name, 'quick', 3, 'brown'), d) "

View File

@ -1301,6 +1301,79 @@ TEST_F(IResearchQueryPhraseTest, test) {
ASSERT_TRUE(result.result.is(TRI_ERROR_BAD_PARAMETER));
}
// test invalid input type (invalid order of terms)
{
auto result = arangodb::tests::executeQuery(
vocbase,
"FOR d IN testView SEARCH PHRASE(d['value'], 1, '12312', '12313') SORT BM25(d) ASC, TFIDF(d) DESC, d.seq RETURN d");
ASSERT_TRUE(result.result.is(TRI_ERROR_BAD_PARAMETER));
}
// test invalid input type (invalid order of terms 2)
{
auto result = arangodb::tests::executeQuery(
vocbase,
"FOR d IN testView SEARCH PHRASE(d['value'], '12312', '12313', 2 ) SORT BM25(d) ASC, TFIDF(d) DESC, d.seq RETURN d");
ASSERT_TRUE(result.result.is(TRI_ERROR_BAD_PARAMETER));
}
// test invalid input type (invalid order of terms 3)
{
auto result = arangodb::tests::executeQuery(
vocbase,
"FOR d IN testView SEARCH PHRASE(d['value'], '12312', 2, 2, '12313') SORT BM25(d) ASC, TFIDF(d) DESC, d.seq RETURN d");
ASSERT_TRUE(result.result.is(TRI_ERROR_BAD_PARAMETER));
}
// test invalid input type (invalid order of terms) [] args
{
auto result = arangodb::tests::executeQuery(
vocbase,
"FOR d IN testView SEARCH PHRASE(d['value'], 1, ['12312'], ['12313']) SORT BM25(d) ASC, TFIDF(d) DESC, d.seq RETURN d");
ASSERT_TRUE(result.result.is(TRI_ERROR_BAD_PARAMETER));
}
// test invalid input type (invalid order of terms 2) [] args
{
auto result = arangodb::tests::executeQuery(
vocbase,
"FOR d IN testView SEARCH PHRASE(d['value'], ['12312'], ['12313'], 2 ) SORT BM25(d) ASC, TFIDF(d) DESC, d.seq RETURN d");
ASSERT_TRUE(result.result.is(TRI_ERROR_BAD_PARAMETER));
}
// test invalid input type (invalid order of terms 3) [] args
{
auto result = arangodb::tests::executeQuery(
vocbase,
"FOR d IN testView SEARCH PHRASE(d['value'], ['12312'], 2, 2, ['12313']) SORT BM25(d) ASC, TFIDF(d) DESC, d.seq RETURN d");
ASSERT_TRUE(result.result.is(TRI_ERROR_BAD_PARAMETER));
}
// test invalid input type (invalid order of terms) via []
{
auto result = arangodb::tests::executeQuery(
vocbase,
"FOR d IN testView SEARCH PHRASE(d['value'], [1, '12312', '12313']) SORT BM25(d) ASC, TFIDF(d) DESC, d.seq RETURN d");
ASSERT_TRUE(result.result.is(TRI_ERROR_BAD_PARAMETER));
}
// test invalid input type (invalid order of terms 2) via []
{
auto result = arangodb::tests::executeQuery(
vocbase,
"FOR d IN testView SEARCH PHRASE(d['value'], ['12312', '12313', 2] ) SORT BM25(d) ASC, TFIDF(d) DESC, d.seq RETURN d");
ASSERT_TRUE(result.result.is(TRI_ERROR_BAD_PARAMETER));
}
// test invalid input type (invalid order of terms 3) via []
{
auto result = arangodb::tests::executeQuery(
vocbase,
"FOR d IN testView SEARCH PHRASE(d['value'], ['12312', 2, 2, '12313']) SORT BM25(d) ASC, TFIDF(d) DESC, d.seq RETURN d");
ASSERT_TRUE(result.result.is(TRI_ERROR_BAD_PARAMETER));
}
// test missing value
{
auto result = arangodb::tests::executeQuery(
@ -1928,8 +2001,8 @@ TEST_F(IResearchQueryPhraseTest, test) {
std::vector<arangodb::velocypack::Slice> expected = {};
auto result = arangodb::tests::executeQuery(
vocbase,
"FOR d IN testView SEARCH ANALYZER(PHRASE(d['duplicated'], [ 'v', 1, "
"'c' ], 'test_analyzer'), 'identity') SORT BM25(d) ASC, TFIDF(d) DESC, "
"FOR d IN testView SEARCH PHRASE(d['duplicated'], [ 'v', 1, "
"'c' ], 'test_analyzer') SORT BM25(d) ASC, TFIDF(d) DESC, "
"d.seq RETURN d");
ASSERT_TRUE(result.result.ok());
auto slice = result.data->slice();
@ -1945,4 +2018,219 @@ TEST_F(IResearchQueryPhraseTest, test) {
EXPECT_EQ(i, expected.size());
}
// test custom analyzer with multiple mixed offsets
{
std::vector<arangodb::velocypack::Slice> expected = {
insertedDocs[6].slice(), insertedDocs[10].slice(),
insertedDocs[16].slice(), insertedDocs[26].slice(),
insertedDocs[32].slice(), insertedDocs[36].slice()
};
auto result = arangodb::tests::executeQuery(
vocbase,
"FOR d IN testView SEARCH PHRASE(d.duplicated, ['a', 'b'], 1, ['d'], "
"'test_analyzer') SORT BM25(d) ASC, TFIDF(d) DESC, d.seq RETURN d");
ASSERT_TRUE(result.result.ok());
auto slice = result.data->slice();
EXPECT_TRUE(slice.isArray());
size_t i = 0;
for (arangodb::velocypack::ArrayIterator itr(slice); itr.valid(); ++itr) {
auto const resolved = itr.value().resolveExternals();
EXPECT_TRUE(i < expected.size());
EXPECT_TRUE((0 == arangodb::basics::VelocyPackHelper::compare(expected[i++],
resolved, true)));
}
EXPECT_EQ(i, expected.size());
}
// test custom analyzer with multiple mixed offsets via []
{
std::vector<arangodb::velocypack::Slice> expected = {
insertedDocs[6].slice(), insertedDocs[10].slice(),
insertedDocs[16].slice(), insertedDocs[26].slice(),
insertedDocs[32].slice(), insertedDocs[36].slice()
};
auto result = arangodb::tests::executeQuery(
vocbase,
"FOR d IN testView SEARCH PHRASE(d.duplicated, ['a', 'b', 1, 'd'], "
"'test_analyzer') SORT BM25(d) ASC, TFIDF(d) DESC, d.seq RETURN d");
ASSERT_TRUE(result.result.ok());
auto slice = result.data->slice();
EXPECT_TRUE(slice.isArray());
size_t i = 0;
for (arangodb::velocypack::ArrayIterator itr(slice); itr.valid(); ++itr) {
auto const resolved = itr.value().resolveExternals();
EXPECT_TRUE(i < expected.size());
EXPECT_TRUE((0 == arangodb::basics::VelocyPackHelper::compare(expected[i++],
resolved, true)));
}
EXPECT_EQ(i, expected.size());
}
// test custom analyzer with multiple mixed offsets
{
std::vector<arangodb::velocypack::Slice> expected = {
insertedDocs[6].slice(), insertedDocs[10].slice(),
insertedDocs[16].slice(), insertedDocs[26].slice(),
insertedDocs[32].slice(), insertedDocs[36].slice()
};
auto result = arangodb::tests::executeQuery(
vocbase,
"FOR d IN testView SEARCH PHRASE(d.duplicated, ['a', 1, 'c'], 0, 'd', "
"'test_analyzer') SORT BM25(d) ASC, TFIDF(d) DESC, d.seq RETURN d");
ASSERT_TRUE(result.result.ok());
auto slice = result.data->slice();
EXPECT_TRUE(slice.isArray());
size_t i = 0;
for (arangodb::velocypack::ArrayIterator itr(slice); itr.valid(); ++itr) {
auto const resolved = itr.value().resolveExternals();
EXPECT_TRUE(i < expected.size());
EXPECT_TRUE((0 == arangodb::basics::VelocyPackHelper::compare(expected[i++],
resolved, true)));
}
EXPECT_EQ(i, expected.size());
}
// test custom analyzer with multiple mixed offsets
{
std::vector<arangodb::velocypack::Slice> expected = {
insertedDocs[6].slice(), insertedDocs[10].slice(),
insertedDocs[16].slice(), insertedDocs[26].slice(),
insertedDocs[32].slice(), insertedDocs[36].slice()
};
auto result = arangodb::tests::executeQuery(
vocbase,
"FOR d IN testView SEARCH PHRASE(d.duplicated, ['a', 'b', 'c'], 0, 'd', "
"'test_analyzer') SORT BM25(d) ASC, TFIDF(d) DESC, d.seq RETURN d");
ASSERT_TRUE(result.result.ok());
auto slice = result.data->slice();
EXPECT_TRUE(slice.isArray());
size_t i = 0;
for (arangodb::velocypack::ArrayIterator itr(slice); itr.valid(); ++itr) {
auto const resolved = itr.value().resolveExternals();
EXPECT_TRUE(i < expected.size());
EXPECT_TRUE((0 == arangodb::basics::VelocyPackHelper::compare(expected[i++],
resolved, true)));
}
EXPECT_EQ(i, expected.size());
}
// test custom analyzer with multiple mixed offsets via []
{
std::vector<arangodb::velocypack::Slice> expected = {
insertedDocs[6].slice(), insertedDocs[10].slice(),
insertedDocs[16].slice(), insertedDocs[26].slice(),
insertedDocs[32].slice(), insertedDocs[36].slice()
};
auto result = arangodb::tests::executeQuery(
vocbase,
"FOR d IN testView SEARCH ANALYZER(PHRASE(d.duplicated, ['a', 1, 'c', 'd']), "
"'test_analyzer') SORT BM25(d) ASC, TFIDF(d) DESC, d.seq RETURN d");
ASSERT_TRUE(result.result.ok());
auto slice = result.data->slice();
EXPECT_TRUE(slice.isArray());
size_t i = 0;
for (arangodb::velocypack::ArrayIterator itr(slice); itr.valid(); ++itr) {
auto const resolved = itr.value().resolveExternals();
EXPECT_TRUE(i < expected.size());
EXPECT_TRUE((0 == arangodb::basics::VelocyPackHelper::compare(expected[i++],
resolved, true)));
}
EXPECT_EQ(i, expected.size());
}
// testarray at first arg
{
std::vector<arangodb::velocypack::Slice> expected = {
insertedDocs[6].slice(), insertedDocs[10].slice(),
insertedDocs[16].slice(), insertedDocs[26].slice(),
insertedDocs[32].slice(), insertedDocs[36].slice()
};
auto result = arangodb::tests::executeQuery(
vocbase,
"FOR d IN testView SEARCH ANALYZER(PHRASE(d.duplicated, ['a', 1, 'c', 'd']), "
"'test_analyzer') SORT BM25(d) ASC, TFIDF(d) DESC, d.seq RETURN d");
ASSERT_TRUE(result.result.ok());
auto slice = result.data->slice();
EXPECT_TRUE(slice.isArray());
size_t i = 0;
for (arangodb::velocypack::ArrayIterator itr(slice); itr.valid(); ++itr) {
auto const resolved = itr.value().resolveExternals();
EXPECT_TRUE(i < expected.size());
EXPECT_TRUE((0 == arangodb::basics::VelocyPackHelper::compare(expected[i++],
resolved, true)));
}
EXPECT_EQ(i, expected.size());
}
// testarray at first arg with analyzer
{
std::vector<arangodb::velocypack::Slice> expected = {
insertedDocs[6].slice(), insertedDocs[10].slice(),
insertedDocs[16].slice(), insertedDocs[26].slice(),
insertedDocs[32].slice(), insertedDocs[36].slice()
};
auto result = arangodb::tests::executeQuery(
vocbase,
"FOR d IN testView SEARCH PHRASE(d.duplicated, ['a', 1, 'c', 'd'], "
"'test_analyzer') SORT BM25(d) ASC, TFIDF(d) DESC, d.seq RETURN d");
ASSERT_TRUE(result.result.ok());
auto slice = result.data->slice();
EXPECT_TRUE(slice.isArray());
size_t i = 0;
for (arangodb::velocypack::ArrayIterator itr(slice); itr.valid(); ++itr) {
auto const resolved = itr.value().resolveExternals();
EXPECT_TRUE(i < expected.size());
EXPECT_TRUE((0 == arangodb::basics::VelocyPackHelper::compare(expected[i++],
resolved, true)));
}
EXPECT_EQ(i, expected.size());
}
// array recursion simple
{
auto result = arangodb::tests::executeQuery(
vocbase,
"FOR d IN testView SEARCH PHRASE(d.prefix, ['b', 1, ['t', 'e', 1, 'a']], "
"'test_analyzer') SORT BM25(d) ASC, TFIDF(d) DESC, d.seq RETURN d");
ASSERT_TRUE(result.result.is(TRI_ERROR_BAD_PARAMETER));
}
// array recursion
{
auto result = arangodb::tests::executeQuery(
vocbase,
"FOR d IN testView SEARCH PHRASE(d.prefix, ['b', 1, ['t', 'e', 1, 'a']], 0, ['d'], 0, ['s', 0, 'f', 's'], 1, [[['a', 1, 'd']]], 0, 'f', "
"'test_analyzer') SORT BM25(d) ASC, TFIDF(d) DESC, d.seq RETURN d");
ASSERT_TRUE(result.result.is(TRI_ERROR_BAD_PARAMETER));
}
// array recursion via []
{
auto result = arangodb::tests::executeQuery(
vocbase,
"FOR d IN testView SEARCH PHRASE(d.prefix, [['b', 1, ['t', 'e', 1, 'a']], 0, ['d'], 0, ['s', 0, 'f', 's'], 1, [[['a', 1, 'd']]], 0, 'f'], "
"'test_analyzer') SORT BM25(d) ASC, TFIDF(d) DESC, d.seq RETURN d");
ASSERT_TRUE(result.result.is(TRI_ERROR_BAD_PARAMETER));
}
// array recursion without analyzer
{
auto result = arangodb::tests::executeQuery(
vocbase,
"FOR d IN testView SEARCH ANALYZER(PHRASE(d.prefix, ['b', 1, ['t', 'e', 1, 'a']], 0, ['d'], 0, ['s', 0, 'f', 's'], 1, [[['a', 1, 'd']]], 0, 'f'), "
"'test_analyzer') SORT BM25(d) ASC, TFIDF(d) DESC, d.seq RETURN d");
ASSERT_TRUE(result.result.is(TRI_ERROR_BAD_PARAMETER));
}
// array recursion without analyzer via []
{
auto result = arangodb::tests::executeQuery(
vocbase,
"FOR d IN testView SEARCH ANALYZER(PHRASE(d.prefix, [['b', 1, ['t', 'e', 1, 'a']], 0, ['d'], 0, ['s', 0, 'f', 's'], 1, [[['a', 1, 'd']]], 0, 'f']), "
"'test_analyzer') SORT BM25(d) ASC, TFIDF(d) DESC, d.seq RETURN d");
ASSERT_TRUE(result.result.is(TRI_ERROR_BAD_PARAMETER));
}
}

View File

@ -703,6 +703,7 @@ void assertFilter(TRI_vocbase_t& vocbase, bool parseOk, bool execOk,
std::shared_ptr<arangodb::velocypack::Builder> bindVars /*= nullptr*/,
std::string const& refName /*= "d"*/
) {
SCOPED_TRACE(testing::Message("assertFilter failed for query:<") << queryString << "> parseOk:" << parseOk << " execOk:" << execOk);
auto options = std::make_shared<arangodb::velocypack::Builder>();
arangodb::aql::Query query(false, vocbase, arangodb::aql::QueryString(queryString),

View File

@ -470,6 +470,26 @@ function iResearchAqlTestSuite () {
assertEqual(result3.length, 1);
assertEqual(result3[0].name, 'full');
var result4 = db._query("FOR doc IN UnitTestsView SEARCH ANALYZER(PHRASE(doc.text, 'quick ', 1, ' fox jumps'), 'text_en') OPTIONS { waitForSync : true } RETURN doc").toArray();
assertEqual(result4.length, 1);
assertEqual(result4[0].name, 'full');
var result5 = db._query("FOR doc IN UnitTestsView SEARCH ANALYZER(PHRASE(doc.text, [ 'quick ', 1, ' fox jumps' ]), 'text_en') OPTIONS { waitForSync : true } RETURN doc").toArray();
assertEqual(result5.length, 1);
assertEqual(result5[0].name, 'full');
var result6 = db._query("FOR doc IN UnitTestsView SEARCH ANALYZER(PHRASE(doc.text, 'quick ', 0, 'brown', 0, [' fox', ' jumps']), 'text_en') OPTIONS { waitForSync : true } RETURN doc").toArray();
assertEqual(result6.length, 1);
assertEqual(result6[0].name, 'full');
var result7 = db._query("FOR doc IN UnitTestsView SEARCH ANALYZER(PHRASE(doc.text, [ 'quick ', 'brown', ' fox jumps' ]), 'text_en') OPTIONS { waitForSync : true } RETURN doc").toArray();
assertEqual(result7.length, 1);
assertEqual(result7[0].name, 'full');
},
testExistsFilter : function () {

View File

@ -196,17 +196,16 @@ function optimizerRuleTestSuite() {
[ "FOR v IN " + colName + " FILTER v.b == 1 SORT v.b, v.a RETURN 1", false, true ],
[ "FOR v IN " + colName + " FILTER v.a == 1 && v.b == 1 SORT v.b, v.a RETURN 1", true, false ],
[ "FOR v IN " + colName + " FILTER v.a == 1 && v.b == 1 SORT v.a, v.b RETURN 1", true, false ],
[ "FOR v IN " + colName + " FILTER v.a == 1 && v.b == 1 SORT v.a, v.c RETURN 1", true, true ],
[ "FOR v IN " + colName + " FILTER v.a == 1 && v.b == 1 SORT v.a, v.c RETURN 1", false, true ],
[ "FOR v IN " + colName + " FILTER v.a == 1 && v.b == 1 SORT v.b, v.a RETURN 1", true, false ],
[ "FOR v IN " + colName + " FILTER v.a == 1 && v.b == 1 SORT v.a, v.b, v.c RETURN 1", true, true ]
[ "FOR v IN " + colName + " FILTER v.a == 1 && v.b == 1 SORT v.a, v.b, v.c RETURN 1", false, true ]
];
queries.forEach(function(query) {
var result = AQL_EXPLAIN(query[0]);
if (query[1]) {
assertNotEqual(-1, removeAlwaysOnClusterRules(result.plan.rules).indexOf(ruleName), query[0]);
}
else {
} else {
assertEqual(-1, removeAlwaysOnClusterRules(result.plan.rules).indexOf(ruleName), query[0]);
}
if (query[2]) {
@ -409,7 +408,6 @@ function optimizerRuleTestSuite() {
// place since the index can't fullfill all of the sorting criteria.
////////////////////////////////////////////////////////////////////////////////
testSortMoreThanIndexed: function () {
var query = "FOR v IN " + colName + " FILTER v.a == 1 SORT v.a, v.c RETURN [v.a, v.b, v.c]";
// no index can be used for v.c -> sort has to remain in place!
var XPresult;
@ -438,7 +436,7 @@ function optimizerRuleTestSuite() {
QResults[2] = AQL_EXECUTE(query, { }, paramIndexFromSort_IndexRange).json;
XPresult = AQL_EXPLAIN(query, { }, paramIndexFromSort_IndexRange);
assertEqual([ ruleName, secondRuleName ], removeAlwaysOnClusterRules(XPresult.plan.rules).sort());
assertEqual([ secondRuleName ], removeAlwaysOnClusterRules(XPresult.plan.rules).sort());
// The sortnode and its calculation node should not have been removed.
hasSortNode(XPresult);
hasCalculationNodes(XPresult, 4);
@ -1193,7 +1191,7 @@ function optimizerRuleTestSuite() {
testSortModifyFilterCondition : function () {
var query = "FOR v IN " + colName + " FILTER v.a == 123 SORT v.a, v.xxx RETURN v";
var rules = AQL_EXPLAIN(query).plan.rules;
assertNotEqual(-1, rules.indexOf(ruleName));
assertEqual(-1, rules.indexOf(ruleName));
assertNotEqual(-1, rules.indexOf(secondRuleName));
assertNotEqual(-1, rules.indexOf("remove-filter-covered-by-index"));
@ -1204,9 +1202,8 @@ function optimizerRuleTestSuite() {
++seen;
assertFalse(node.reverse);
} else if (node.type === "SortNode") {
// first sort condition (v.a) should have been removed because it is const
++seen;
assertEqual(1, node.elements.length);
assertEqual(2, node.elements.length);
}
});
assertEqual(2, seen);