diff --git a/src/include/expression_evaluator/expression_evaluator.h b/src/include/expression_evaluator/expression_evaluator.h index dec1f28d6a..01a340d508 100644 --- a/src/include/expression_evaluator/expression_evaluator.h +++ b/src/include/expression_evaluator/expression_evaluator.h @@ -84,7 +84,12 @@ class ExpressionEvaluator { bool updateSelectedPos(common::SelectionVector& selVector) const { auto& resultSelVector = resultVector->state->getSelVector(); - if (resultSelVector.getSelSize() > 1) { + // The boolean-only path below is valid only for genuinely flat result states. It must + // not be taken for an unflat chunk whose current selection happens to have size 1: the + // caller may activate the selection buffer via setToFiltered afterwards, so skipping + // the buffer write would expose stale positions left over from a previous batch + // (issue #692: a rel-property IN filter duplicated one row and dropped another). + if (!resultVector->state->isFlat()) { auto numSelectedValues = 0u; for (auto i = 0u; i < resultSelVector.getSelSize(); ++i) { auto pos = resultSelVector[i]; diff --git a/test/test_files/issue/issue692.test b/test/test_files/issue/issue692.test index 210c3f268b..38a48649b6 100644 --- a/test/test_files/issue/issue692.test +++ b/test/test_files/issue/issue692.test @@ -67,3 +67,56 @@ FileA|Function1|CALLS|0.850000 -STATEMENT MATCH (s:Function)-[r:CodeRelation]->(t) WHERE t.id = 'Function1' AND r.type IN ['CALLS'] RETURN s.id AS src, t.id AS dst, r.type AS type, r.confidence AS conf; ---- 1 Function2|Function1|CALLS|0.850000 + +-CASE Issue692InPredicateStaleSelectionAfterCopy +# Deterministic reproduction for github issue #692 via the bulk COPY path. +# Shape that triggers the stale-selection bug in the no-selectFunc filter +# fallback (updateSelectedPos): the File->Function pair holds two edges +# between the SAME node pair (DEFINES before CALLS, so the matching row is +# not at position 0), followed by a single-row Function->Function batch. +# Before the fix, the IN filter returned the f0 row twice and dropped F10. + +-STATEMENT CREATE NODE TABLE Function(id STRING, PRIMARY KEY(id)); +---- ok +-STATEMENT CREATE NODE TABLE File(id STRING, PRIMARY KEY(id)); +---- ok +-STATEMENT CREATE REL TABLE CodeRelation(FROM File TO Function, FROM Function TO Function, type STRING); +---- ok +-STATEMENT COPY Function FROM "${LBUG_ROOT_DIRECTORY}/test/test_files/issue/issue692_fixture/function.csv" (header=false); +---- ok +-STATEMENT COPY File FROM "${LBUG_ROOT_DIRECTORY}/test/test_files/issue/issue692_fixture/file.csv" (header=false); +---- ok +-STATEMENT COPY CodeRelation FROM "${LBUG_ROOT_DIRECTORY}/test/test_files/issue/issue692_fixture/file_fn.csv" (from='File', to='Function', header=false); +---- ok +-STATEMENT COPY CodeRelation FROM "${LBUG_ROOT_DIRECTORY}/test/test_files/issue/issue692_fixture/fn_fn.csv" (from='Function', to='Function', header=false); +---- ok + +# Ground truth: three incoming edges on F11 +-STATEMENT MATCH (c)-[r:CodeRelation]->(t {id: 'F11'}) RETURN c.id AS src, r.type AS type ORDER BY src, type; +---- 3 +F10|CALLS +f0|CALLS +f0|DEFINES + +# Equality predicate (was already correct) +-STATEMENT MATCH (c)-[r:CodeRelation]->(t {id: 'F11'}) WHERE r.type = 'CALLS' RETURN c.id AS src, r.type AS type ORDER BY src; +---- 2 +F10|CALLS +f0|CALLS + +# Single-element IN predicate (the bug: returned f0 twice, dropped F10) +-STATEMENT MATCH (c)-[r:CodeRelation]->(t {id: 'F11'}) WHERE r.type IN ['CALLS'] RETURN c.id AS src, r.type AS type ORDER BY src; +---- 2 +F10|CALLS +f0|CALLS + +# list_contains spelled directly (same code path) +-STATEMENT MATCH (c)-[r:CodeRelation]->(t {id: 'F11'}) WHERE list_contains(['CALLS'], r.type) RETURN c.id AS src, r.type AS type ORDER BY src; +---- 2 +F10|CALLS +f0|CALLS + +# Complement stays correct +-STATEMENT MATCH (c)-[r:CodeRelation]->(t {id: 'F11'}) WHERE NOT (r.type IN ['CALLS']) RETURN c.id AS src, r.type AS type; +---- 1 +f0|DEFINES diff --git a/test/test_files/issue/issue692_fixture/file.csv b/test/test_files/issue/issue692_fixture/file.csv new file mode 100644 index 0000000000..844dc808a1 --- /dev/null +++ b/test/test_files/issue/issue692_fixture/file.csv @@ -0,0 +1 @@ +f0 diff --git a/test/test_files/issue/issue692_fixture/file_fn.csv b/test/test_files/issue/issue692_fixture/file_fn.csv new file mode 100644 index 0000000000..cc9de8f980 --- /dev/null +++ b/test/test_files/issue/issue692_fixture/file_fn.csv @@ -0,0 +1,2 @@ +f0,F11,DEFINES +f0,F11,CALLS diff --git a/test/test_files/issue/issue692_fixture/fn_fn.csv b/test/test_files/issue/issue692_fixture/fn_fn.csv new file mode 100644 index 0000000000..8130515f78 --- /dev/null +++ b/test/test_files/issue/issue692_fixture/fn_fn.csv @@ -0,0 +1,3 @@ +F8,F9,CALLS +F10,F11,CALLS +F12,F13,CALLS diff --git a/test/test_files/issue/issue692_fixture/function.csv b/test/test_files/issue/issue692_fixture/function.csv new file mode 100644 index 0000000000..ef82f7bb49 --- /dev/null +++ b/test/test_files/issue/issue692_fixture/function.csv @@ -0,0 +1,6 @@ +F8 +F9 +F10 +F11 +F12 +F13