Skip to content

Commit b6729da

Browse files
authored
Merge main into develop (#5422)
2 parents 1d7a3db + 37d6654 commit b6729da

7 files changed

Lines changed: 785 additions & 192 deletions

File tree

AGENTS.md

Lines changed: 427 additions & 171 deletions
Large diffs are not rendered by default.

core/queryalgebra/evaluation/src/main/java/org/eclipse/rdf4j/query/algebra/evaluation/ArrayBindingSet.java

Lines changed: 6 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -143,10 +143,7 @@ public Function<ArrayBindingSet, Binding> getDirectGetBinding(String bindingName
143143
}
144144
return a -> {
145145
Value value = a.values[index];
146-
if (value == NULL_VALUE) {
147-
value = null;
148-
}
149-
if (value != null) {
146+
if (value != null && value != NULL_VALUE) {
150147
return new SimpleBinding(bindingName, value);
151148
} else {
152149
return null;
@@ -416,7 +413,7 @@ public ArrayBindingSetIterator() {
416413
@Override
417414
public boolean hasNext() {
418415
while (index < values.length) {
419-
if (values[index] != null) {
416+
if (values[index] != null && values[index] != NULL_VALUE) {
420417
return true;
421418
}
422419
index++;
@@ -426,20 +423,10 @@ public boolean hasNext() {
426423

427424
@Override
428425
public Binding next() {
429-
while (index < values.length) {
430-
if (values[index] != null) {
431-
String name = bindingNames[index];
432-
Value value = values[index++];
433-
if (value == NULL_VALUE) {
434-
value = null;
435-
}
436-
if (value != null) {
437-
return new SimpleBinding(name, value);
438-
} else {
439-
return null;
440-
}
441-
}
442-
index++;
426+
if (hasNext()) {
427+
String name = bindingNames[index];
428+
Value value = values[index++];
429+
return new SimpleBinding(name, value);
443430
}
444431

445432
throw new NoSuchElementException();

core/queryalgebra/evaluation/src/main/java/org/eclipse/rdf4j/query/algebra/evaluation/optimizer/ProjectionRemovalOptimizer.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@ private ProjectionFinder() {
7979
@Override
8080
public void meet(Projection node) throws RuntimeException {
8181
super.meet(node);
82+
8283
VariableFinder findVariables = new VariableFinder();
8384
node.visit(findVariables);
8485

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
/**
2+
* ******************************************************************************
3+
* Copyright (c) 2025 Eclipse RDF4J contributors.
4+
*
5+
* All rights reserved. This program and the accompanying materials
6+
* are made available under the terms of the Eclipse Distribution License v1.0
7+
* which accompanies this distribution, and is available at
8+
* http://www.eclipse.org/org/documents/edl-v10.php.
9+
*
10+
* SPDX-License-Identifier: BSD-3-Clause
11+
* ******************************************************************************
12+
*/
13+
package org.eclipse.rdf4j.query.algebra.evaluation;
14+
15+
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
16+
import static org.junit.jupiter.api.Assertions.assertEquals;
17+
import static org.junit.jupiter.api.Assertions.assertFalse;
18+
import static org.junit.jupiter.api.Assertions.assertNotNull;
19+
import static org.junit.jupiter.api.Assertions.assertTrue;
20+
21+
import java.util.LinkedHashSet;
22+
import java.util.Set;
23+
24+
import org.eclipse.rdf4j.model.vocabulary.OWL;
25+
import org.eclipse.rdf4j.query.Binding;
26+
import org.junit.jupiter.api.Test;
27+
28+
/**
29+
* Reproduces a NullPointerException when an ArrayBindingSet contains an explicit null (UNDEF) binding value and is
30+
* copied into a QueryBindingSet. Prior to the fix, iterating the ArrayBindingSet could yield a null Binding, which
31+
* caused NPE in QueryBindingSet.addBinding.
32+
*/
33+
public class ArrayBindingSetNullHandlingTest {
34+
35+
@Test
36+
public void iteratorShouldNotReturnNullBindings() {
37+
ArrayBindingSet bs = new ArrayBindingSet("myVar", "unbound", "mappingProp", "const");
38+
// Explicitly set an UNDEF/null binding using the direct setter (stores a sentinel NULL_VALUE)
39+
bs.getDirectSetBinding("myVar").accept(null, bs);
40+
// Add a real binding so iteration has at least one valid element
41+
bs.getDirectSetBinding("mappingProp").accept(OWL.EQUIVALENTCLASS, bs);
42+
43+
for (Binding b : bs) {
44+
assertNotNull(b, "iterator must not yield null Binding elements");
45+
}
46+
}
47+
48+
@Test
49+
public void copyingToQueryBindingSetMustSkipUndefBindings() {
50+
ArrayBindingSet bs = new ArrayBindingSet("myVar", "unbound", "mappingProp", "const");
51+
// myVar is explicitly present with UNDEF value
52+
bs.getDirectSetBinding("myVar").accept(null, bs);
53+
// mappingProp has a concrete value
54+
bs.getDirectSetBinding("mappingProp").accept(OWL.EQUIVALENTCLASS, bs);
55+
56+
// Creating a QueryBindingSet from the ArrayBindingSet should not throw
57+
QueryBindingSet qbs = assertDoesNotThrow(() -> new QueryBindingSet(bs));
58+
59+
assertTrue(qbs.hasBinding("mappingProp"));
60+
assertEquals(OWL.EQUIVALENTCLASS, qbs.getValue("mappingProp"));
61+
// UNDEF binding must not appear in the resulting set
62+
assertFalse(qbs.hasBinding("myVar"));
63+
assertEquals(1, qbs.size());
64+
}
65+
66+
}

core/queryparser/sparql/src/main/java/org/eclipse/rdf4j/query/parser/sparql/WildcardProjectionProcessor.java

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,8 +58,12 @@ public static void process(ASTOperationContainer container) throws MalformedQuer
5858
whereClause.jjtAccept(collector, null);
5959

6060
Set<ASTSelect> selectClauses = collector.getSelectClauses();
61+
// process nested SELECT wildcards deepest-first so inner subqueries are expanded
62+
// before their parents collect variables
63+
java.util.List<ASTSelect> ordered = new java.util.ArrayList<>(selectClauses);
64+
ordered.sort(java.util.Comparator.comparingInt(WildcardProjectionProcessor::depth).reversed());
6165

62-
for (ASTSelect selectClause : selectClauses) {
66+
for (ASTSelect selectClause : ordered) {
6367
if (selectClause.isWildcard()) {
6468
ASTSelectQuery q = (ASTSelectQuery) selectClause.jjtGetParent();
6569

@@ -95,6 +99,16 @@ public static void process(ASTOperationContainer container) throws MalformedQuer
9599
}
96100
}
97101

102+
private static int depth(Node n) {
103+
int d = 0;
104+
Node p = n.jjtGetParent();
105+
while (p != null) {
106+
d++;
107+
p = p.jjtGetParent();
108+
}
109+
return d;
110+
}
111+
98112
private static void addQueryVars(ASTWhereClause queryBody, Node wildcardNode) throws MalformedQueryException {
99113
QueryVariableCollector visitor = new QueryVariableCollector();
100114

0 commit comments

Comments
 (0)