diff --git a/src/cypher/cypher.c b/src/cypher/cypher.c index 4f554ea4..ecf200d2 100644 --- a/src/cypher/cypher.c +++ b/src/cypher/cypher.c @@ -4105,7 +4105,7 @@ static void build_return_columns(result_builder_t *rb, cbm_return_clause_t *ret) static void execute_return_simple(cbm_return_clause_t *ret, binding_t *bindings, int bind_count, int max_rows, result_builder_t *rb) { int proj_cap = max_rows; - if (ret->limit > 0 && !ret->order_by && ret->skip <= 0) { + if (ret->limit > 0 && !ret->distinct && !ret->order_by && ret->skip <= 0) { proj_cap = ret->limit; } for (int bi = 0; bi < bind_count && rb->row_count < proj_cap; bi++) { @@ -4387,11 +4387,11 @@ static void execute_return_clause(cbm_query_t *q, cbm_return_clause_t *ret, bind } } - rb_apply_order_by(rb, ret); - rb_apply_skip_limit(rb, ret->skip, ret->limit >= 0 ? ret->limit : max_rows); if (ret->distinct) { rb_apply_distinct(rb); } + rb_apply_order_by(rb, ret); + rb_apply_skip_limit(rb, ret->skip, ret->limit >= 0 ? ret->limit : max_rows); } static int execute_single(cbm_store_t *store, cbm_query_t *q, const char *project, int max_rows, diff --git a/tests/test_cypher.c b/tests/test_cypher.c index 5fe3cd59..29e4e741 100644 --- a/tests/test_cypher.c +++ b/tests/test_cypher.c @@ -2524,6 +2524,52 @@ TEST(cypher_issue237_distinct_order_limit) { PASS(); } +/* #873: duplicate projected rows must be deduped before ORDER BY + LIMIT */ +TEST(cypher_issue873_distinct_order_limit_dedupes_before_limit) { + cbm_store_t *s = setup_cypher_store(); + cbm_cypher_result_t r = {0}; + int rc = cbm_cypher_execute( + s, "MATCH (n) RETURN DISTINCT n.label AS label ORDER BY label LIMIT 2", "test", 0, &r); + ASSERT_EQ(rc, 0); + ASSERT_NULL(r.error); + ASSERT_EQ(r.row_count, 2); + ASSERT_STR_EQ(r.rows[0][0], "Function"); + ASSERT_STR_EQ(r.rows[1][0], "Module"); + cbm_cypher_result_free(&r); + cbm_store_close(s); + PASS(); +} + +/* #873: early LIMIT must not truncate rows before DISTINCT for simple RETURN */ +TEST(cypher_issue873_distinct_limit_dedupes_before_limit) { + cbm_store_t *s = setup_cypher_store(); + cbm_cypher_result_t r = {0}; + int rc = cbm_cypher_execute( + s, "MATCH (n) RETURN DISTINCT n.label AS label LIMIT 2", "test", 0, &r); + ASSERT_EQ(rc, 0); + ASSERT_NULL(r.error); + ASSERT_EQ(r.row_count, 2); + cbm_cypher_result_free(&r); + cbm_store_close(s); + PASS(); +} + +/* #873: SKIP is applied after DISTINCT and ORDER BY, not before dedupe */ +TEST(cypher_issue873_distinct_order_skip_limit_dedupes_before_skip) { + cbm_store_t *s = setup_cypher_store(); + cbm_cypher_result_t r = {0}; + int rc = cbm_cypher_execute( + s, "MATCH (n) RETURN DISTINCT n.label AS label ORDER BY label SKIP 1 LIMIT 1", "test", + 0, &r); + ASSERT_EQ(rc, 0); + ASSERT_NULL(r.error); + ASSERT_EQ(r.row_count, 1); + ASSERT_STR_EQ(r.rows[0][0], "Module"); + cbm_cypher_result_free(&r); + cbm_store_close(s); + PASS(); +} + /* #252: toInteger() */ TEST(cypher_issue252_tointeger) { cbm_store_t *s = setup_cypher_store(); @@ -2624,6 +2670,9 @@ SUITE(cypher) { RUN_TEST(cypher_exec_match_all_functions); RUN_TEST(cypher_issue240_labels_function); RUN_TEST(cypher_issue237_distinct_order_limit); + RUN_TEST(cypher_issue873_distinct_order_limit_dedupes_before_limit); + RUN_TEST(cypher_issue873_distinct_limit_dedupes_before_limit); + RUN_TEST(cypher_issue873_distinct_order_skip_limit_dedupes_before_skip); RUN_TEST(cypher_issue252_tointeger); RUN_TEST(cypher_issue305_count_star_alias); RUN_TEST(cypher_exec_where_eq);