From dcb4b3ec16955e9658c567574eb50e1a6f132588 Mon Sep 17 00:00:00 2001
From: AyoubKAZ <kazarayoub2004@gmail.com>
Date: Wed, 22 Jul 2026 02:24:46 +0200
Subject: [PATCH] Add support for label conjunction (&) in SQL/PGQ

The SQL/PGQ standard allows label expressions to use boolean operators,
such as conjunction of labels: MATCH (a IS label1 & label2). Previously, only
disjunction (|) was supported in graph element patterns.

Supporting label conjunction, disjunction and in the future ; label negation, requires an evaluation of a full label expression. Previously get_path_elements_for_path_factor() considered only flat BoolExpr evaluation (as it only knew about disjunctions).
As required, this commit adds a recursive label expression evaluator, get_path_elements_for_path_factor() now fetches all candidate table elements for the path factor kind once and evaluates each table element's label OIDs against the label expression tree.

This also allows to support label conjunction for a path factor appearing with different label expressions in multiple path elements.
---
 src/backend/parser/gram.y                 |  20 ++
 src/backend/parser/scan.l                 |   4 +-
 src/backend/rewrite/rewriteGraphTable.c   | 273 +++++++++++++++-------
 src/test/regress/expected/graph_table.out |  41 +++-
 src/test/regress/sql/graph_table.sql      |  11 +
 5 files changed, 265 insertions(+), 84 deletions(-)

diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 17035fb4d15..0ebc4a232c1 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -726,6 +726,8 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 				label_expression
 				label_disjunction
 				label_term
+				label_conjunction
+				label_factor
 %type <str>		opt_colid
 
 /*
@@ -941,6 +943,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 %nonassoc	IDENT PARTITION RANGE ROWS GROUPS PRECEDING FOLLOWING CUBE ROLLUP
 			SET KEYS OBJECT_P SCALAR TO USING VALUE_P WITH WITHOUT PATH
 %left		Op OPERATOR RIGHT_ARROW '|'	/* multi-character ops and user-defined operators */
+%left		'&'							/* bitwise AND / label conjunction */
 %left		'+' '-'
 %left		'*' '/' '%'
 %left		'^'
@@ -15981,6 +15984,8 @@ a_expr:		c_expr									{ $$ = $1; }
 				{ $$ = (Node *) makeSimpleA_Expr(AEXPR_OP, "->", $1, $3, @2); }
 			| a_expr '|' a_expr
 				{ $$ = (Node *) makeSimpleA_Expr(AEXPR_OP, "|", $1, $3, @2); }
+			| a_expr '&' a_expr
+				{ $$ = (Node *) makeSimpleA_Expr(AEXPR_OP, "&", $1, $3, @2); }
 
 			| a_expr qual_Op a_expr				%prec Op
 				{ $$ = (Node *) makeA_Expr(AEXPR_OP, $2, $1, $3, @2); }
@@ -16469,6 +16474,8 @@ b_expr:		c_expr
 				{ $$ = (Node *) makeSimpleA_Expr(AEXPR_OP, "->", $1, $3, @2); }
 			| b_expr '|' b_expr
 				{ $$ = (Node *) makeSimpleA_Expr(AEXPR_OP, "|", $1, $3, @2); }
+			| b_expr '&' b_expr
+				{ $$ = (Node *) makeSimpleA_Expr(AEXPR_OP, "&", $1, $3, @2); }
 			| b_expr qual_Op b_expr				%prec Op
 				{ $$ = (Node *) makeA_Expr(AEXPR_OP, $2, $1, $3, @2); }
 			| qual_Op b_expr					%prec Op
@@ -17666,6 +17673,7 @@ MathOp:		 '+'									{ $$ = "+"; }
 			| NOT_EQUALS							{ $$ = "<>"; }
 			| RIGHT_ARROW							{ $$ = "->"; }
 			| '|'									{ $$ = "|"; }
+			| '&'									{ $$ = "&"; }
 		;
 
 qual_Op:	Op
@@ -18449,8 +18457,20 @@ label_disjunction:
 		;
 
 label_term:
+			label_factor
+			| label_conjunction
+		;
+
+label_conjunction:
+			label_term '&' label_factor
+				{ $$ = makeAndExpr($1, $3, @2); }
+		;
+
+label_factor:
 			name
 				{ $$ = makeColumnRef($1, NIL, @1, yyscanner); }
+			| '(' label_expression ')'
+				{ $$ = $2; }
 		;
 
 
diff --git a/src/backend/parser/scan.l b/src/backend/parser/scan.l
index ee6c34cc14b..5b8e3f1f681 100644
--- a/src/backend/parser/scan.l
+++ b/src/backend/parser/scan.l
@@ -361,7 +361,7 @@ right_arrow		"->"
  * If you change either set, adjust the character lists appearing in the
  * rule for "operator"!
  */
-self			[,()\[\].;\:\|\+\-\*\/\%\^\<\>\=]
+self			[,()\[\].;\:\|\&\+\-\*\/\%\^\<\>\=]
 op_chars		[\~\!\@\#\^\&\|\`\?\+\-\*\/\%\<\>\=]
 operator		{op_chars}+
 
@@ -937,7 +937,7 @@ other			.
 						 * that the "self" rule would have.
 						 */
 						if (nchars == 1 &&
-							strchr(",()[].;:|+-*/%^<>=", yytext[0]))
+							strchr(",()[].;:|&+-*/%^<>=", yytext[0]))
 							return yytext[0];
 						/*
 						 * Likewise, if what we have left is two chars, and
diff --git a/src/backend/rewrite/rewriteGraphTable.c b/src/backend/rewrite/rewriteGraphTable.c
index cdb1f4c0dca..1020130268d 100644
--- a/src/backend/rewrite/rewriteGraphTable.c
+++ b/src/backend/rewrite/rewriteGraphTable.c
@@ -214,7 +214,7 @@ generate_queries_for_path_pattern(RangeTblEntry *rte, List *path_pattern)
 
 				/*
 				 * If both the element patterns have label expressions, they
-				 * need to be conjuncted, which is not supported right now.
+				 * need to be conjuncted.
 				 *
 				 * However, an empty label expression means all labels.
 				 * Conjunction of any label expression with all labels is the
@@ -224,10 +224,10 @@ generate_queries_for_path_pattern(RangeTblEntry *rte, List *path_pattern)
 				if (!other->labelexpr)
 					other->labelexpr = gep->labelexpr;
 				else if (gep->labelexpr && !equal(other->labelexpr, gep->labelexpr))
-					ereport(ERROR,
-							(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-							 errmsg("element patterns with same variable name \"%s\" but different label expressions are not supported",
-									gep->variable)));
+					other->labelexpr = (Node *) makeBoolExpr(AND_EXPR,
+															 list_make2(other->labelexpr,
+																		gep->labelexpr),
+															 -1);
 
 				/*
 				 * If two element patterns have the same variable name, they
@@ -824,6 +824,9 @@ create_pe_for_element(struct path_factor *pf, Oid elemoid)
 /*
  * Returns the list of OIDs of graph labels which the given label expression
  * resolves to in the given property graph.
+ *
+ * For a disjunction (OR) or conjunction (AND) the returned list is the union
+ * of the OIDs of all leaf label references in the expression tree.
  */
 static List *
 get_labels_for_expr(Oid propgraphid, Node *labelexpr)
@@ -869,11 +872,15 @@ get_labels_for_expr(Oid propgraphid, Node *labelexpr)
 	else if (IsA(labelexpr, BoolExpr))
 	{
 		BoolExpr   *be = castNode(BoolExpr, labelexpr);
-		List	   *label_exprs = be->args;
 
+		/*
+		 * Recursively collect all leaf label OIDs from both OR (disjunction)
+		 * and AND (conjunction) expressions.
+		 */
 		label_oids = NIL;
-		foreach_node(GraphLabelRef, glr, label_exprs)
-			label_oids = lappend_oid(label_oids, glr->labelid);
+		foreach_node(Node, arg, be->args)
+			label_oids = list_concat(label_oids,
+									 get_labels_for_expr(propgraphid, arg));
 	}
 	else
 	{
@@ -888,29 +895,13 @@ get_labels_for_expr(Oid propgraphid, Node *labelexpr)
 }
 
 /*
- * Return a list of all the graph elements that satisfy the graph element pattern
- * represented by the given path_factor `pf`.
- *
- * First we find all the graph labels that satisfy the label expression in path
- * factor. Each label is associated with one or more graph elements.  A union of
- * all such elements satisfies the element pattern. We create one path_element
- * object representing every element whose graph element kind qualifies the
- * element pattern kind. A list of all such path_element objects is returned.
- *
- * Note that we need to report an error for an explicitly specified label which
- * is not associated with any graph element of the required kind. So we have to
- * treat each label separately. Without that requirement we could have collected
- * all the unique elements first and then created path_element objects for them
- * to simplify the code.
+ * Return a list of all element OIDs associated with the given property graph
+ * that match the specified kind (VERTEX_PATTERN or EDGE_PATTERN).
  */
 static List *
-get_path_elements_for_path_factor(Oid propgraphid, struct path_factor *pf)
+get_all_elements_for_kind(Oid propgraphid, uint32 kind)
 {
-	List	   *label_oids = get_labels_for_expr(propgraphid, pf->labelexpr);
-	List	   *elem_oids_seen = NIL;
-	List	   *pf_elem_oids = NIL;
-	List	   *path_elements = NIL;
-	List	   *unresolved_labels = NIL;
+	List	   *elem_oids = NIL;
 	Relation	rel;
 	SysScanDesc scan;
 	ScanKeyData key[1];
@@ -921,81 +912,187 @@ get_path_elements_for_path_factor(Oid propgraphid, struct path_factor *pf)
 	 * of path factors like nested path pattern need to be handled separately
 	 * when supported.
 	 */
-	Assert(pf->kind == VERTEX_PATTERN || IS_EDGE_PATTERN(pf->kind));
+	Assert(kind == VERTEX_PATTERN || IS_EDGE_PATTERN(kind));
+
+	rel = table_open(PropgraphElementRelationId, AccessShareLock);
+	ScanKeyInit(&key[0],
+				Anum_pg_propgraph_element_pgepgid,
+				BTEqualStrategyNumber,
+				F_OIDEQ, ObjectIdGetDatum(propgraphid));
+	scan = systable_beginscan(rel, PropgraphElementAliasIndexId, true,
+							  NULL, 1, key);
+	while (HeapTupleIsValid(tup = systable_getnext(scan)))
+	{
+		Form_pg_propgraph_element elem = (Form_pg_propgraph_element) GETSTRUCT(tup);
+
+		if (elem->pgekind == 'v' && kind == VERTEX_PATTERN)
+			elem_oids = lappend_oid(elem_oids, elem->oid);
+		else if (elem->pgekind == 'e' && IS_EDGE_PATTERN(kind))
+			elem_oids = lappend_oid(elem_oids, elem->oid);
+	}
+	systable_endscan(scan);
+	table_close(rel, AccessShareLock);
+
+	return elem_oids;
+}
+
+/*
+ * Return a list of label OIDs of all labels attached to the given property
+ * graph element.
+ */
+static List *
+get_labels_for_element(Oid elem_oid)
+{
+	List	   *label_oids = NIL;
+	Relation	rel;
+	SysScanDesc scan;
+	ScanKeyData key[1];
+	HeapTuple	tup;
 
 	rel = table_open(PropgraphElementLabelRelationId, AccessShareLock);
-	foreach_oid(labeloid, label_oids)
+	ScanKeyInit(&key[0],
+				Anum_pg_propgraph_element_label_pgelelid,
+				BTEqualStrategyNumber,
+				F_OIDEQ, ObjectIdGetDatum(elem_oid));
+	scan = systable_beginscan(rel, PropgraphElementLabelElementLabelIndexId, true,
+							  NULL, 1, key);
+	while (HeapTupleIsValid(tup = systable_getnext(scan)))
 	{
-		bool		found = false;
+		Form_pg_propgraph_element_label label_elem =
+			(Form_pg_propgraph_element_label) GETSTRUCT(tup);
 
-		ScanKeyInit(&key[0],
-					Anum_pg_propgraph_element_label_pgellabelid,
-					BTEqualStrategyNumber,
-					F_OIDEQ, ObjectIdGetDatum(labeloid));
-		scan = systable_beginscan(rel, PropgraphElementLabelLabelIndexId, true,
-								  NULL, 1, key);
-		while (HeapTupleIsValid(tup = systable_getnext(scan)))
+		label_oids = lappend_oid(label_oids, label_elem->pgellabelid);
+	}
+	systable_endscan(scan);
+	table_close(rel, AccessShareLock);
+
+	return label_oids;
+}
+
+/*
+ * Recursively evaluate a label expression AST node against the set of label
+ * OIDs carried by a candidate graph element.  Returns true if the element
+ * satisfies the expression.
+ *
+ * NULL labelexpr means no IS clause was specified, which per SQL/PGQ matches
+ * any element.
+ */
+static bool
+eval_label_expr_for_element(Node *labelexpr, List *elem_labels)
+{
+	if (labelexpr == NULL)
+		return true;
+
+	if (IsA(labelexpr, GraphLabelRef))
+	{
+		GraphLabelRef *lref = castNode(GraphLabelRef, labelexpr);
+
+		return list_member_oid(elem_labels, lref->labelid);
+	}
+
+	if (IsA(labelexpr, BoolExpr))
+	{
+		BoolExpr   *bexpr = (BoolExpr *) labelexpr;
+
+		switch (bexpr->boolop)
 		{
-			Form_pg_propgraph_element_label label_elem = (Form_pg_propgraph_element_label) GETSTRUCT(tup);
-			Oid			elem_oid = label_elem->pgelelid;
+			case AND_EXPR:
+				foreach_node(Node, arg, bexpr->args)
+				{
+					if (!eval_label_expr_for_element(arg, elem_labels))
+						return false;
+				}
+				return true;
 
-			if (!list_member_oid(elem_oids_seen, elem_oid))
-			{
-				/*
-				 * Create path_element object if the new element qualifies the
-				 * element pattern kind.
-				 */
-				struct path_element *pe = create_pe_for_element(pf, elem_oid);
+			case OR_EXPR:
+				foreach_node(Node, arg, bexpr->args)
+				{
+					if (eval_label_expr_for_element(arg, elem_labels))
+						return true;
+				}
+				return false;
 
-				if (pe)
+			case NOT_EXPR:
 				{
-					path_elements = lappend(path_elements, pe);
+					Node	   *arg = linitial(bexpr->args);
 
-					/* Remember qualified elements. */
-					pf_elem_oids = lappend_oid(pf_elem_oids, elem_oid);
-					found = true;
+					return !eval_label_expr_for_element(arg, elem_labels);
 				}
 
-				/*
-				 * Remember qualified and unqualified elements processed so
-				 * far to avoid processing already processed elements again.
-				 */
-				elem_oids_seen = lappend_oid(elem_oids_seen, label_elem->pgelelid);
-			}
-			else if (list_member_oid(pf_elem_oids, elem_oid))
-			{
-				/*
-				 * The graph element is known to qualify the given element
-				 * pattern. Flag that the current label has at least one
-				 * qualified element associated with it.
-				 */
-				found = true;
-			}
+			default:
+				elog(ERROR, "unrecognized boolop in label expression: %d", bexpr->boolop);
+		}
+	}
+
+	return false;
+}
+
+/*
+ * Return a list of all the graph elements that satisfy the graph element pattern
+ * represented by the given path_factor `pf`.
+ *
+ * Each candidate element of the appropriate kind (vertex or edge) is tested
+ * against the label expression by eval_label_expr_for_element().  Labels that
+ * appear in the expression but are not associated with any element of the
+ * requested kind cause an error when the label was explicitly named, or are
+ * silently dropped from pf->labeloids when the element pattern had no IS
+ * clause.
+ */
+static List *
+get_path_elements_for_path_factor(Oid propgraphid, struct path_factor *pf)
+{
+	List	   *path_elements = NIL;
+	List	   *all_candidate_elem_oids;
+	List	   *label_oids;
+	List	   *all_candidate_labels = NIL;
+	List	   *unresolved_labels = NIL;
+
+	all_candidate_elem_oids = get_all_elements_for_kind(propgraphid, pf->kind);
+
+	foreach_oid(elem_oid, all_candidate_elem_oids)
+	{
+		List	   *elem_labels = get_labels_for_element(elem_oid);
+
+		/*
+		 * Accumulate the union of all labels seen across every candidate
+		 * element so that we can later detect labels that are referenced in
+		 * the expression but have no associated elements.
+		 */
+		all_candidate_labels = list_concat_unique_oid(all_candidate_labels, elem_labels);
+
+		if (eval_label_expr_for_element(pf->labelexpr, elem_labels))
+		{
+			struct path_element *pe = create_pe_for_element(pf, elem_oid);
+
+			Assert(pe != NULL);
+			path_elements = lappend(path_elements, pe);
 		}
+	}
+
+	/*
+	 * Validate that every label referenced in the expression is associated
+	 * with at least one element of the requested kind.  If the IS clause was
+	 * explicit and a label has no matching elements, raise an error.  If the
+	 * element pattern had no IS clause (all-labels), collect such labels as
+	 * unresolved so that they can be excluded from pf->labeloids.
+	 */
+	label_oids = get_labels_for_expr(propgraphid, pf->labelexpr);
 
-		if (!found)
+	foreach_oid(labeloid, label_oids)
+	{
+		if (!list_member_oid(all_candidate_labels, labeloid))
 		{
-			/*
-			 * We did not find any qualified element associated with this
-			 * label. The label or its properties can not be associated with
-			 * the given element pattern. Throw an error if the label was
-			 * explicitly specified in the element pattern. Otherwise remember
-			 * it for later use.
-			 */
-			if (!pf->labelexpr)
-				unresolved_labels = lappend_oid(unresolved_labels, labeloid);
-			else
+			if (pf->labelexpr)
 				ereport(ERROR,
 						(errcode(ERRCODE_UNDEFINED_OBJECT),
 						 errmsg("no property graph element of type \"%s\" has label \"%s\" associated with it in property graph \"%s\"",
 								pf->kind == VERTEX_PATTERN ? "vertex" : "edge",
 								get_propgraph_label_name(labeloid),
 								get_rel_name(propgraphid))));
+			else
+				unresolved_labels = lappend_oid(unresolved_labels, labeloid);
 		}
-
-		systable_endscan(scan);
 	}
-	table_close(rel, AccessShareLock);
 
 	/*
 	 * Remove the labels which were not explicitly mentioned in the label
@@ -1005,6 +1102,20 @@ get_path_elements_for_path_factor(Oid propgraphid, struct path_factor *pf)
 	 */
 	pf->labeloids = list_difference_oid(label_oids, unresolved_labels);
 
+	if (path_elements == NIL && pf->labelexpr != NULL)
+	{
+		if (pf->variable)
+			ereport(ERROR,
+					(errcode(ERRCODE_UNDEFINED_OBJECT),
+					 errmsg("label expression for variable \"%s\" is not satisfied in property graph \"%s\"",
+							pf->variable, get_rel_name(propgraphid))));
+		else
+			ereport(ERROR,
+					(errcode(ERRCODE_UNDEFINED_OBJECT),
+					 errmsg("label expression is not satisfied in property graph \"%s\"",
+							get_rel_name(propgraphid))));
+	}
+
 	return path_elements;
 }
 
diff --git a/src/test/regress/expected/graph_table.out b/src/test/regress/expected/graph_table.out
index 46566b2e32f..d1dd46e1612 100644
--- a/src/test/regress/expected/graph_table.out
+++ b/src/test/regress/expected/graph_table.out
@@ -629,7 +629,7 @@ SELECT * FROM GRAPH_TABLE (g1 MATCH (a WHERE a.vprop1 between 20 and 2000)->(b W
 SELECT * FROM GRAPH_TABLE (g1 MATCH (a IS l1)-[a IS l1]->(b IS l1) COLUMNS (a.ename AS aename, b.ename AS bename)) ORDER BY 1, 2; -- error
 ERROR:  element patterns with same variable name "a" but different element pattern types
 SELECT * FROM GRAPH_TABLE (g1 MATCH (a IS vl1)->(b)->(a IS vl2) WHERE a.vname <> b.vname COLUMNS (a.vname AS self, b.vname AS through, a.vprop1 AS self_p1, b.vprop1 AS through_p1)) ORDER BY self, through;  -- error
-ERROR:  element patterns with same variable name "a" but different label expressions are not supported
+ERROR:  label expression for variable "a" is not satisfied in property graph "g1"
 SELECT * FROM GRAPH_TABLE (g1 MATCH (a IS vl1)->(b)->(a) COLUMNS (a.vname AS self, b.vname AS through, a.vprop1 AS self_p1, b.vprop1 AS through_p1)) ORDER BY self, through;
  self | through | self_p1 | through_p1 
 ------+---------+---------+------------
@@ -644,6 +644,45 @@ SELECT * FROM GRAPH_TABLE (g1 MATCH (a)->(b)->(a IS vl1) COLUMNS (a.vname AS sel
  v13  | v23     |      30 |       1030
 (2 rows)
 
+-- implicit label conjunction: v2 carries both labels
+SELECT * FROM GRAPH_TABLE (g1 MATCH (a IS vl2)->(b)->(a IS vl3) COLUMNS (a.vname AS self, b.vname AS through, a.vprop2 AS vl2_prop, a.vprop1 AS vl3_prop)) ORDER BY self, through;
+ self | through | vl2_prop | vl3_prop 
+------+---------+----------+----------
+ v21  | v12     |     1100 |     1010
+ v22  | v32     |     1200 |     1020
+ v23  | v13     |     1300 |     1030
+(3 rows)
+
+-- explicit label conjunction
+SELECT * FROM GRAPH_TABLE (g1 MATCH (a IS vl2 & vl3)->(b) COLUMNS (a.vname AS self, b.vname AS through)) ORDER BY self, through;
+ self | through 
+------+---------
+ v21  | v12
+ v22  | v32
+ v23  | v13
+(3 rows)
+
+-- explicit conjunction with empty intersection
+SELECT * FROM GRAPH_TABLE (g1 MATCH (a IS vl1 & vl2)->(b) COLUMNS (a.vname AS self, b.vname AS through)) ORDER BY self, through; -- error
+ERROR:  label expression for variable "a" is not satisfied in property graph "g1"
+-- explicit conjunction with disjunction
+SELECT * FROM GRAPH_TABLE (g1 MATCH (a IS (vl1 | vl3) & l1) COLUMNS (a.elname AS aname)) ORDER BY aname;
+ aname 
+-------
+ v11
+ v12
+ v13
+ v21
+ v22
+ v23
+ v31
+ v32
+ v33
+(9 rows)
+
+-- conjunction with a wrong-kind label on a vertex
+SELECT * FROM GRAPH_TABLE (g1 MATCH (a IS vl1 & el1)->(b) COLUMNS (a.vname AS self, b.vname AS through)); -- error
+ERROR:  no property graph element of type "vertex" has label "el1" associated with it in property graph "g1"
 -- add loop to test edge patterns with same variable name
 CREATE TABLE e3_3 (
     src_id int,
diff --git a/src/test/regress/sql/graph_table.sql b/src/test/regress/sql/graph_table.sql
index 3fb0e50ddb2..576ada321ba 100644
--- a/src/test/regress/sql/graph_table.sql
+++ b/src/test/regress/sql/graph_table.sql
@@ -379,6 +379,17 @@ SELECT * FROM GRAPH_TABLE (g1 MATCH (a IS vl1)->(b)->(a IS vl2) WHERE a.vname <>
 SELECT * FROM GRAPH_TABLE (g1 MATCH (a IS vl1)->(b)->(a) COLUMNS (a.vname AS self, b.vname AS through, a.vprop1 AS self_p1, b.vprop1 AS through_p1)) ORDER BY self, through;
 SELECT * FROM GRAPH_TABLE (g1 MATCH (a)->(b)->(a IS vl1) COLUMNS (a.vname AS self, b.vname AS through, a.vprop1 AS self_p1, b.vprop1 AS through_p1)) ORDER BY self, through;
 
+-- implicit label conjunction: v2 carries both labels
+SELECT * FROM GRAPH_TABLE (g1 MATCH (a IS vl2)->(b)->(a IS vl3) COLUMNS (a.vname AS self, b.vname AS through, a.vprop2 AS vl2_prop, a.vprop1 AS vl3_prop)) ORDER BY self, through;
+-- explicit label conjunction
+SELECT * FROM GRAPH_TABLE (g1 MATCH (a IS vl2 & vl3)->(b) COLUMNS (a.vname AS self, b.vname AS through)) ORDER BY self, through;
+-- explicit conjunction with empty intersection
+SELECT * FROM GRAPH_TABLE (g1 MATCH (a IS vl1 & vl2)->(b) COLUMNS (a.vname AS self, b.vname AS through)) ORDER BY self, through; -- error
+-- explicit conjunction with disjunction
+SELECT * FROM GRAPH_TABLE (g1 MATCH (a IS (vl1 | vl3) & l1) COLUMNS (a.elname AS aname)) ORDER BY aname;
+-- conjunction with a wrong-kind label on a vertex
+SELECT * FROM GRAPH_TABLE (g1 MATCH (a IS vl1 & el1)->(b) COLUMNS (a.vname AS self, b.vname AS through)); -- error
+
 -- add loop to test edge patterns with same variable name
 CREATE TABLE e3_3 (
     src_id int,
-- 
2.34.1

