IrSelect.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;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.function.UnaryOperator;
import org.eclipse.rdf4j.query.algebra.Var;
/**
* Textual IR for a SELECT query (header + WHERE + trailing modifiers).
*
* The WHERE body is an {@link IrBGP}. Header sections keep rendered expressions as text to preserve the exact surface
* form chosen by the renderer.
*/
public class IrSelect extends IrNode {
private final List<IrProjectionItem> projection = new ArrayList<>();
private final List<IrGroupByElem> groupBy = new ArrayList<>();
private final List<String> having = new ArrayList<>();
private final List<IrOrderSpec> orderBy = new ArrayList<>();
private boolean distinct;
private boolean reduced;
private IrBGP where;
private long limit = -1;
private long offset = -1;
public IrSelect(boolean newScope) {
super(newScope);
}
public void setDistinct(boolean distinct) {
this.distinct = distinct;
}
public void setReduced(boolean reduced) {
this.reduced = reduced;
}
public List<IrProjectionItem> getProjection() {
return projection;
}
public IrBGP getWhere() {
return where;
}
public void setWhere(IrBGP bgp) {
this.where = bgp;
}
public List<IrGroupByElem> getGroupBy() {
return groupBy;
}
public List<String> getHaving() {
return having;
}
public List<IrOrderSpec> getOrderBy() {
return orderBy;
}
public long getLimit() {
return limit;
}
public void setLimit(long limit) {
this.limit = limit;
}
public long getOffset() {
return offset;
}
public void setOffset(long offset) {
this.offset = offset;
}
@Override
public IrNode transformChildren(UnaryOperator<IrNode> op) {
IrBGP newWhere = this.where;
if (newWhere != null) {
IrNode t = op.apply(newWhere);
if (t instanceof IrBGP) {
newWhere = (IrBGP) t;
}
}
IrSelect copy = new IrSelect(this.isNewScope());
copy.setDistinct(this.distinct);
copy.setReduced(this.reduced);
copy.getProjection().addAll(this.projection);
copy.setWhere(newWhere);
copy.getGroupBy().addAll(this.groupBy);
copy.getHaving().addAll(this.having);
copy.getOrderBy().addAll(this.orderBy);
copy.setLimit(this.limit);
copy.setOffset(this.offset);
return copy;
}
@Override
public void print(IrPrinter p) {
// SELECT header (keep WHERE on the same line for canonical formatting)
StringBuilder hdr = new StringBuilder(64);
hdr.append("SELECT ");
if (distinct) {
hdr.append("DISTINCT ");
} else if (reduced) {
hdr.append("REDUCED ");
}
if (projection.isEmpty()) {
hdr.append("*");
} else {
for (int i = 0; i < projection.size(); i++) {
IrProjectionItem it = projection.get(i);
if (it.getExprText() == null) {
hdr.append('?').append(it.getVarName());
} else {
hdr.append('(').append(it.getExprText()).append(" AS ?").append(it.getVarName()).append(')');
}
if (i + 1 < projection.size()) {
hdr.append(' ');
}
}
}
p.startLine();
p.append(hdr.toString());
p.append(" WHERE ");
// WHERE
if (where != null) {
where.print(p);
} else {
p.openBlock();
p.closeBlock();
}
// GROUP BY
if (!groupBy.isEmpty()) {
StringBuilder gb = new StringBuilder("GROUP BY");
for (IrGroupByElem g : groupBy) {
if (g.getExprText() == null) {
gb.append(' ').append('?').append(g.getVarName());
} else {
gb.append(" (").append(g.getExprText()).append(" AS ?").append(g.getVarName()).append(")");
}
}
p.line(gb.toString());
}
// HAVING
if (!having.isEmpty()) {
StringBuilder hv = new StringBuilder("HAVING");
for (String cond : having) {
String t = cond == null ? "" : cond.trim();
// Add parentheses when not already a single wrapped expression
if (!t.isEmpty() && !(t.startsWith("(") && t.endsWith(")"))) {
t = "(" + t + ")";
}
hv.append(' ').append(t);
}
p.line(hv.toString());
}
// ORDER BY
if (!orderBy.isEmpty()) {
StringBuilder ob = new StringBuilder("ORDER BY");
for (IrOrderSpec o : orderBy) {
if (o.isAscending()) {
ob.append(' ').append(o.getExprText());
} else {
ob.append(" DESC(").append(o.getExprText()).append(')');
}
}
p.line(ob.toString());
}
// LIMIT / OFFSET
if (limit >= 0) {
p.line("LIMIT " + limit);
}
if (offset >= 0) {
p.line("OFFSET " + offset);
}
}
@Override
public Set<Var> getVars() {
if (where != null) {
return where.getVars();
}
return Collections.emptySet();
}
public boolean isDistinct() {
return distinct;
}
public boolean isReduced() {
return reduced;
}
}