@@ -1673,6 +1673,35 @@ TEST(cypher_exec_count) {
16731673 PASS ();
16741674}
16751675
1676+ TEST (cypher_exec_optional_bound_terminal_count_ignores_unbound ) {
1677+ cbm_store_t * s = setup_cypher_store ();
1678+ cbm_cypher_result_t r = {0 };
1679+
1680+ int rc = cbm_cypher_execute (s ,
1681+ "MATCH (f:Function) OPTIONAL MATCH (t)-[:CALLS]->(f) "
1682+ "RETURN f.name, COUNT(t) AS cnt" ,
1683+ "test" , 0 , & r );
1684+ ASSERT_EQ (rc , 0 );
1685+ ASSERT_EQ (r .row_count , 4 );
1686+
1687+ for (int i = 0 ; i < r .row_count ; i ++ ) {
1688+ const char * name = r .rows [i ][0 ];
1689+ const char * count = r .rows [i ][1 ];
1690+ if (strcmp (name , "HandleOrder" ) == 0 ) {
1691+ ASSERT_STR_EQ (count , "0" );
1692+ } else if (strcmp (name , "ValidateOrder" ) == 0 || strcmp (name , "SubmitOrder" ) == 0 ||
1693+ strcmp (name , "LogError" ) == 0 ) {
1694+ ASSERT_STR_EQ (count , "1" );
1695+ } else {
1696+ ASSERT_TRUE (false);
1697+ }
1698+ }
1699+
1700+ cbm_cypher_result_free (& r );
1701+ cbm_store_close (s );
1702+ PASS ();
1703+ }
1704+
16761705TEST (cypher_exec_limit ) {
16771706 cbm_store_t * s = setup_cypher_store ();
16781707 cbm_cypher_result_t r = {0 };
@@ -2078,9 +2107,10 @@ TEST(cypher_optional_match_target_still_allowed_issue1919) {
20782107TEST (cypher_with_alias_stays_in_scope_issue1919 ) {
20792108 cbm_store_t * s = setup_cypher_store ();
20802109 cbm_cypher_result_t r = {0 };
2081- int rc = cbm_cypher_execute (
2082- s , "MATCH (f:Function)-[:CALLS]->(g) WITH f.name AS caller, g AS callee RETURN caller, callee.name" ,
2083- "test" , 0 , & r );
2110+ int rc = cbm_cypher_execute (s ,
2111+ "MATCH (f:Function)-[:CALLS]->(g) WITH f.name AS caller, g AS "
2112+ "callee RETURN caller, callee.name" ,
2113+ "test" , 0 , & r );
20842114 ASSERT_EQ (rc , 0 );
20852115 ASSERT_NULL (r .error );
20862116 ASSERT_TRUE (r .row_count > 0 );
@@ -2828,6 +2858,74 @@ TEST(cypher_exec_where_not) {
28282858 PASS ();
28292859}
28302860
2861+ /* Regression (distilled from PR #1245, Andrew Hundt): the early WHERE pass in
2862+ * execute_single runs on seed rows with only the first alias bound. A
2863+ * comparison on an unbound alias used to evaluate to true, which NOT / XOR
2864+ * then inverted -- so every seed was pruned before CALLS expansion and the
2865+ * query returned zero rows. Unbound leaves must stay UNKNOWN there. */
2866+ TEST (cypher_exec_where_not_on_relationship_target ) {
2867+ cbm_store_t * s = setup_cypher_store ();
2868+ cbm_cypher_result_t r = {0 };
2869+ int rc = cbm_cypher_execute (s ,
2870+ "MATCH (a:Function)-[:CALLS]->(b:Function) "
2871+ "WHERE NOT b.name CONTAINS \"Order\" RETURN b.name" ,
2872+ "test" , 0 , & r );
2873+ ASSERT_EQ (rc , 0 );
2874+ ASSERT_EQ (r .row_count , 1 );
2875+ ASSERT_STR_EQ (r .rows [0 ][0 ], "LogError" );
2876+ cbm_cypher_result_free (& r );
2877+ cbm_store_close (s );
2878+ PASS ();
2879+ }
2880+
2881+ TEST (cypher_exec_where_mixed_alias_and ) {
2882+ cbm_store_t * s = setup_cypher_store ();
2883+ cbm_cypher_result_t r = {0 };
2884+ int rc = cbm_cypher_execute (
2885+ s ,
2886+ "MATCH (a:Function)-[:CALLS]->(b:Function) "
2887+ "WHERE a.name = \"HandleOrder\" AND NOT b.name CONTAINS \"Order\" RETURN b.name" ,
2888+ "test" , 0 , & r );
2889+ ASSERT_EQ (rc , 0 );
2890+ ASSERT_EQ (r .row_count , 1 );
2891+ ASSERT_STR_EQ (r .rows [0 ][0 ], "LogError" );
2892+ cbm_cypher_result_free (& r );
2893+ cbm_store_close (s );
2894+ PASS ();
2895+ }
2896+
2897+ TEST (cypher_exec_where_mixed_alias_or ) {
2898+ cbm_store_t * s = setup_cypher_store ();
2899+ cbm_cypher_result_t r = {0 };
2900+ int rc = cbm_cypher_execute (
2901+ s ,
2902+ "MATCH (a:Function)-[:CALLS]->(b:Function) "
2903+ "WHERE a.name = \"NoSuchFunction\" OR NOT b.name CONTAINS \"Order\" RETURN b.name" ,
2904+ "test" , 0 , & r );
2905+ ASSERT_EQ (rc , 0 );
2906+ ASSERT_EQ (r .row_count , 1 );
2907+ ASSERT_STR_EQ (r .rows [0 ][0 ], "LogError" );
2908+ cbm_cypher_result_free (& r );
2909+ cbm_store_close (s );
2910+ PASS ();
2911+ }
2912+
2913+ TEST (cypher_exec_where_mixed_alias_xor ) {
2914+ cbm_store_t * s = setup_cypher_store ();
2915+ cbm_cypher_result_t r = {0 };
2916+ int rc =
2917+ cbm_cypher_execute (s ,
2918+ "MATCH (a:Function)-[:CALLS]->(b:Function) "
2919+ "WHERE a.name = \"HandleOrder\" XOR b.name = \"LogError\" RETURN b.name" ,
2920+ "test" , 0 , & r );
2921+ ASSERT_EQ (rc , 0 );
2922+ ASSERT_EQ (r .row_count , 1 );
2923+ ASSERT_STR_EQ (r .rows [0 ][0 ], "ValidateOrder" );
2924+ cbm_cypher_result_free (& r );
2925+ cbm_store_close (s );
2926+ PASS ();
2927+ }
2928+
28312929TEST (cypher_exec_where_in ) {
28322930 cbm_store_t * s = setup_cypher_store ();
28332931 cbm_cypher_result_t r = {0 };
@@ -4368,6 +4466,7 @@ SUITE(cypher) {
43684466 RUN_TEST (cypher_exec_calls_with_where );
43694467 RUN_TEST (cypher_exec_inbound );
43704468 RUN_TEST (cypher_exec_count );
4469+ RUN_TEST (cypher_exec_optional_bound_terminal_count_ignores_unbound );
43714470 RUN_TEST (cypher_exec_limit );
43724471 RUN_TEST (cypher_exec_order_by );
43734472 RUN_TEST (cypher_exec_variable_length );
@@ -4418,6 +4517,10 @@ SUITE(cypher) {
44184517 RUN_TEST (cypher_exec_where_neq_bang );
44194518 RUN_TEST (cypher_exec_where_ends_with );
44204519 RUN_TEST (cypher_exec_where_not );
4520+ RUN_TEST (cypher_exec_where_not_on_relationship_target );
4521+ RUN_TEST (cypher_exec_where_mixed_alias_and );
4522+ RUN_TEST (cypher_exec_where_mixed_alias_or );
4523+ RUN_TEST (cypher_exec_where_mixed_alias_xor );
44214524 RUN_TEST (cypher_exec_where_in );
44224525 RUN_TEST (cypher_exec_where_not_in );
44234526 RUN_TEST (cypher_exec_where_is_null );
0 commit comments