LiftPathUnionScopeInsideGraphTransform.java
/*******************************************************************************
* Copyright (c) 2025 Eclipse RDF4J contributors.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Distribution License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/org/documents/edl-v10.php.
*
* SPDX-License-Identifier: BSD-3-Clause
*******************************************************************************/
package org.eclipse.rdf4j.queryrender.sparql.ir.util.transform;
import java.util.ArrayList;
import java.util.List;
import org.eclipse.rdf4j.queryrender.sparql.ir.IrBGP;
import org.eclipse.rdf4j.queryrender.sparql.ir.IrGraph;
import org.eclipse.rdf4j.queryrender.sparql.ir.IrNode;
import org.eclipse.rdf4j.queryrender.sparql.ir.IrSubSelect;
import org.eclipse.rdf4j.queryrender.sparql.ir.IrUnion;
/**
* Inside GRAPH bodies, lift the scope marker from a path-generated UNION (branches all non-scoped) to the containing
* BGP. This preserves brace grouping when the UNION is later fused into a single path triple.
*
* Strictly limited to GRAPH bodies; no other heuristics.
*/
public final class LiftPathUnionScopeInsideGraphTransform extends BaseTransform {
private LiftPathUnionScopeInsideGraphTransform() {
}
public static IrBGP apply(IrBGP bgp) {
if (bgp == null) {
return null;
}
List<IrNode> out = new ArrayList<>();
for (IrNode n : bgp.getLines()) {
IrNode m = n;
if (n instanceof IrGraph) {
IrGraph g = (IrGraph) n;
m = new IrGraph(g.getGraph(), liftInGraph(g.getWhere()), g.isNewScope());
} else if (n instanceof IrSubSelect) {
// keep as-is
} else if (n instanceof IrUnion) {
IrUnion u = (IrUnion) n;
IrUnion u2 = new IrUnion(u.isNewScope());
for (IrBGP b : u.getBranches()) {
u2.addBranch(apply(b));
}
m = u2;
} else if (n instanceof IrBGP) {
m = apply((IrBGP) n);
} else {
// Generic recursion for container nodes
m = BaseTransform.rewriteContainers(n, LiftPathUnionScopeInsideGraphTransform::apply);
}
out.add(m);
}
return BaseTransform.bgpWithLines(bgp, out);
}
private static IrBGP liftInGraph(IrBGP where) {
if (where == null) {
return null;
}
// If the GRAPH body consists of exactly one UNION whose branches all have newScope=false,
// set the body's newScope to true so braces are preserved post-fuse.
if (where.getLines().size() == 1 && where.getLines().get(0) instanceof IrUnion) {
IrUnion u = (IrUnion) where.getLines().get(0);
boolean allBranchesNonScoped = true;
for (IrBGP b : u.getBranches()) {
if (b != null && b.isNewScope()) {
allBranchesNonScoped = false;
break;
}
}
if (allBranchesNonScoped) {
IrBGP res = new IrBGP(false);
res.add(u);
return res;
}
}
return where;
}
}