@@ -209,6 +209,71 @@ TEST(cypher_parse_simple_node) {
209209 PASS ();
210210}
211211
212+ /* Trailing input must be an error, never a silent drop. The parser used to
213+ * stop at the first thing it did not understand and report success, so the
214+ * engine answered from the fragment it had parsed. */
215+ TEST (cypher_parse_rejects_trailing_tokens ) {
216+ cbm_query_t * q = NULL ;
217+ char * err = NULL ;
218+ int rc = cbm_cypher_parse ("MATCH (f:Function) RETURN f.name AS n BANANA SPLIT 99" , & q , & err );
219+ ASSERT_NEQ (rc , 0 );
220+ ASSERT_NOT_NULL (err );
221+ ASSERT_NULL (q );
222+
223+ /* The message must name what actually stopped the parse, and must not
224+ * mention WITH: this query has no WITH in it anywhere. */
225+ ASSERT (strstr (err , "BANANA" ) != NULL );
226+ ASSERT (strstr (err , "WITH" ) == NULL );
227+
228+ free (err );
229+ PASS ();
230+ }
231+
232+ /* Only one WITH is supported. A second one used to take the rest of the
233+ * query with it — the filter and the RETURN both vanished, and every row
234+ * came back unfiltered under the default projection. */
235+ TEST (cypher_parse_rejects_second_with_clause ) {
236+ cbm_query_t * q = NULL ;
237+ char * err = NULL ;
238+ int rc = cbm_cypher_parse ("MATCH (f:Function) "
239+ "OPTIONAL MATCH (a)-[:CALLS]->(f) "
240+ "WITH f, count(a) AS calls "
241+ "OPTIONAL MATCH (b)-[:USAGE]->(f) "
242+ "WITH f, calls, count(b) AS usages "
243+ "WHERE calls = 0 AND usages = 0 "
244+ "RETURN f.name AS n" ,
245+ & q , & err );
246+ ASSERT_NEQ (rc , 0 );
247+ ASSERT_NOT_NULL (err );
248+ ASSERT_NULL (q );
249+
250+ /* Here the note earns its place. The parse stops at OPTIONAL, and the
251+ * reason is the second WITH further along, which the reader cannot see
252+ * from the stopping point alone. */
253+ ASSERT (strstr (err , "only one WITH clause is supported" ) != NULL );
254+
255+ free (err );
256+ PASS ();
257+ }
258+
259+ /* The guard must not reject a query that is simply finished. One WITH, a
260+ * WHERE after it and a RETURN is the shape the grammar does support. */
261+ TEST (cypher_parse_accepts_single_with_clause ) {
262+ cbm_query_t * q = NULL ;
263+ char * err = NULL ;
264+ int rc = cbm_cypher_parse ("MATCH (f:Function) "
265+ "WITH f, f.name AS n "
266+ "WHERE n = 'buildTree' "
267+ "RETURN n" ,
268+ & q , & err );
269+ ASSERT_EQ (rc , 0 );
270+ ASSERT_NULL (err );
271+ ASSERT_NOT_NULL (q );
272+
273+ cbm_query_free (q );
274+ PASS ();
275+ }
276+
212277TEST (cypher_parse_relationship_outbound ) {
213278 cbm_query_t * q = NULL ;
214279 char * err = NULL ;
@@ -4102,6 +4167,9 @@ SUITE(cypher) {
41024167 RUN_TEST (cypher_lex_full_query );
41034168 /* Parser */
41044169 RUN_TEST (cypher_parse_simple_node );
4170+ RUN_TEST (cypher_parse_rejects_trailing_tokens );
4171+ RUN_TEST (cypher_parse_rejects_second_with_clause );
4172+ RUN_TEST (cypher_parse_accepts_single_with_clause );
41054173 RUN_TEST (cypher_parse_relationship_outbound );
41064174 RUN_TEST (cypher_parse_relationship_inbound );
41074175 RUN_TEST (cypher_parse_relationship_any );
0 commit comments