Skip to content

Commit 42b8b78

Browse files
authored
GH-5834 Verify LMDB theme count bindings (#5835)
2 parents 84868a6 + 009d9c1 commit 42b8b78

4 files changed

Lines changed: 153 additions & 12 deletions

File tree

core/sail/lmdb/src/test/java/org/eclipse/rdf4j/sail/lmdb/benchmark/ThemeQueryBenchmark.java

Lines changed: 45 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
import java.io.FileOutputStream;
2020
import java.io.IOException;
2121
import java.lang.reflect.Method;
22+
import java.util.OptionalLong;
2223
import java.util.Properties;
2324
import java.util.concurrent.TimeUnit;
2425

@@ -31,6 +32,7 @@
3132
import org.eclipse.rdf4j.benchmark.rio.util.ThemeDataSetGenerator;
3233
import org.eclipse.rdf4j.benchmark.rio.util.ThemeDataSetGenerator.Theme;
3334
import org.eclipse.rdf4j.common.transaction.IsolationLevels;
35+
import org.eclipse.rdf4j.model.Literal;
3436
import org.eclipse.rdf4j.model.Statement;
3537
import org.eclipse.rdf4j.model.util.Values;
3638
import org.eclipse.rdf4j.model.vocabulary.RDF;
@@ -96,6 +98,7 @@ public class ThemeQueryBenchmark {
9698
private static final String VALUES_DATA_SIZE_PROPERTY = "values.data.mdb.size.bytes";
9799
private static final String TRIPLE_INDEXES_PROPERTY = "triple.indexes";
98100
private static final String PROFILING_PROPERTY = "rdf4j.benchmark.profiling";
101+
private static final String COUNT_BINDING_NAME = "count";
99102
static final String WAIT_FOR_SKETCHES_PROPERTY = "rdf4j.lmdb.themeQueryBenchmark.waitForSketches";
100103
static final String WAIT_FOR_SKETCHES_TIMEOUT_SECONDS_PROPERTY = "rdf4j.lmdb.themeQueryBenchmark.waitForSketchesTimeoutSeconds";
101104
private static final long DEFAULT_WAIT_FOR_SKETCHES_TIMEOUT_SECONDS = 60L;
@@ -177,22 +180,60 @@ public void setup() throws IOException {
177180
public long executeQuery() {
178181
try (var connection = repository.getConnection()) {
179182
long count;
183+
OptionalLong expectedCountBindingValue = ThemeQueryCatalog.expectedCountBindingValueFor(theme,
184+
z_queryIndex);
180185
TupleQuery tupleQuery = connection.prepareTupleQuery(query);
181186
tupleQuery.setMaxExecutionTime(70);
182187
try (var evaluate = tupleQuery.evaluate()) {
183-
count = evaluate
184-
.stream()
185-
.count();
188+
count = countRowsAndVerifyCountBinding(evaluate, expectedCountBindingValue);
186189
}
187190

188191
if (count != expected) {
189-
throw new IllegalStateException("Unexpected count: expected " + expected + " but got " + count);
192+
throw new IllegalStateException(
193+
"Unexpected result row count for " + queryDescription() + ": expected " + expected
194+
+ " but got " + count);
190195
}
191196

192197
return count;
193198
}
194199
}
195200

201+
private long countRowsAndVerifyCountBinding(TupleQueryResult evaluate, OptionalLong expectedCountBindingValue) {
202+
long count = 0;
203+
while (evaluate.hasNext()) {
204+
var bindingSet = evaluate.next();
205+
count++;
206+
if (expectedCountBindingValue.isPresent()) {
207+
if (count > 1) {
208+
throw new IllegalStateException("Expected exactly one count row for " + queryDescription()
209+
+ " but saw at least " + count + " rows");
210+
}
211+
var value = bindingSet.getValue(COUNT_BINDING_NAME);
212+
if (!(value instanceof Literal literal)) {
213+
throw new IllegalStateException("Expected literal ?" + COUNT_BINDING_NAME + " binding for "
214+
+ queryDescription() + " but got " + value);
215+
}
216+
long actualCountBindingValue = literal.longValue();
217+
long expectedValue = expectedCountBindingValue.getAsLong();
218+
if (actualCountBindingValue != expectedValue) {
219+
throw new IllegalStateException("Unexpected ?" + COUNT_BINDING_NAME + " binding for "
220+
+ queryDescription() + ": expected " + expectedValue + " but got "
221+
+ actualCountBindingValue);
222+
}
223+
}
224+
}
225+
if (expectedCountBindingValue.isPresent() && count == 0) {
226+
throw new IllegalStateException("Expected exactly one count row for " + queryDescription()
227+
+ " but query returned no rows");
228+
}
229+
return count;
230+
}
231+
232+
private String queryDescription() {
233+
return themeName + " query " + z_queryIndex + " ("
234+
+ ThemeQueryCatalog.benchmarkQueryFor(theme, z_queryIndex).getName() + ")";
235+
}
236+
196237
private void ensureDataLoadedAndValidated() throws IOException {
197238
var expectedDbFileSizes = readExpectedDbFileSizes();
198239
if (!hasExpectedDbFileSizes(expectedDbFileSizes)) {

core/sail/lmdb/src/test/java/org/eclipse/rdf4j/sail/lmdb/benchmark/ThemeQueryBenchmarkSmokeIT.java

Lines changed: 26 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ class ThemeQueryBenchmarkSmokeIT {
4949
private static final String PHARMA_HAS_RESULT_LABEL = "pharma-hasResult";
5050
private static final String PHARMA_HAS_ARM_LABEL = "pharma-hasArm";
5151
private static final String PHARMA_P_VALUE_FILTER = "pharma-pValue-filter";
52+
private static final int QUERY_EXECUTION_REPETITIONS = 5;
5253

5354
@Test
5455
void executeQueryReturnsExpectedCountForMedicalRecordsQueryTwo() throws Exception {
@@ -58,23 +59,21 @@ void executeQueryReturnsExpectedCountForMedicalRecordsQueryTwo() throws Exceptio
5859

5960
benchmark.setup();
6061
try {
61-
assertEquals(ThemeQueryCatalog.expectedCountFor(Theme.MEDICAL_RECORDS, 2), benchmark.executeQuery());
62+
assertBenchmarkQueryCount(benchmark, Theme.MEDICAL_RECORDS, 2);
6263
} finally {
6364
benchmark.tearDown();
6465
}
6566
}
6667

6768
@Test
68-
void executeQueryTwiceReturnsExpectedCountForMedicalRecordsQueryTwo() throws Exception {
69+
void executeQueryRepeatedlyReturnsExpectedCountForMedicalRecordsQueryTwo() throws Exception {
6970
ThemeQueryBenchmark benchmark = new ThemeQueryBenchmark();
7071
benchmark.themeName = Theme.MEDICAL_RECORDS.name();
7172
benchmark.z_queryIndex = 2;
7273

7374
benchmark.setup();
7475
try {
75-
long expected = ThemeQueryCatalog.expectedCountFor(Theme.MEDICAL_RECORDS, 2);
76-
assertEquals(expected, benchmark.executeQuery());
77-
assertEquals(expected, benchmark.executeQuery());
76+
assertBenchmarkQueryCount(benchmark, Theme.MEDICAL_RECORDS, 2);
7877
} finally {
7978
benchmark.tearDown();
8079
}
@@ -85,6 +84,16 @@ void executeQueryReturnsExpectedCountForPharmaQueryOne() throws Exception {
8584
assertThemeQueryCount(Theme.PHARMA, 1);
8685
}
8786

87+
@Test
88+
void executeQueryVerifiesExpectedCountBindingForPharmaQueryZero() throws Exception {
89+
assertThemeQueryCount(Theme.PHARMA, 0);
90+
}
91+
92+
@Test
93+
void catalogRecordsExpectedCountBindingForPharmaQueryZero() {
94+
assertEquals(18L, ThemeQueryCatalog.expectedCountBindingValueFor(Theme.PHARMA, 0).orElseThrow());
95+
}
96+
8897
@Test
8998
void executeQueryReturnsExpectedCountForPharmaQueryTwo() throws Exception {
9099
assertThemeQueryCount(Theme.PHARMA, 2);
@@ -100,7 +109,7 @@ void secondBenchmarkTrialReusesPersistedJoinEstimatorSnapshot() throws Exception
100109
first.z_queryIndex = 0;
101110
first.setup();
102111
try {
103-
assertEquals(ThemeQueryCatalog.expectedCountFor(Theme.MEDICAL_RECORDS, 0), first.executeQuery());
112+
assertBenchmarkQueryCount(first, Theme.MEDICAL_RECORDS, 0);
104113
} finally {
105114
first.tearDown();
106115
}
@@ -119,7 +128,7 @@ void secondBenchmarkTrialReusesPersistedJoinEstimatorSnapshot() throws Exception
119128
second.z_queryIndex = 1;
120129
second.setup();
121130
try {
122-
assertEquals(ThemeQueryCatalog.expectedCountFor(Theme.MEDICAL_RECORDS, 1), second.executeQuery());
131+
assertBenchmarkQueryCount(second, Theme.MEDICAL_RECORDS, 1);
123132
} finally {
124133
second.tearDown();
125134
}
@@ -137,12 +146,21 @@ private static void assertThemeQueryCount(Theme theme, int queryIndex) throws Ex
137146

138147
benchmark.setup();
139148
try {
140-
assertEquals(ThemeQueryCatalog.expectedCountFor(theme, queryIndex), benchmark.executeQuery());
149+
assertBenchmarkQueryCount(benchmark, theme, queryIndex);
141150
} finally {
142151
benchmark.tearDown();
143152
}
144153
}
145154

155+
private static void assertBenchmarkQueryCount(ThemeQueryBenchmark benchmark, Theme theme, int queryIndex) {
156+
long expected = ThemeQueryCatalog.expectedCountFor(theme, queryIndex);
157+
for (int repetition = 1; repetition <= QUERY_EXECUTION_REPETITIONS; repetition++) {
158+
assertEquals(expected, benchmark.executeQuery(),
159+
"Unexpected row count for " + theme + " query " + queryIndex + " on repetition " + repetition
160+
+ " of " + QUERY_EXECUTION_REPETITIONS);
161+
}
162+
}
163+
146164
private static List<Path> persistedSketchFiles(Path store) throws Exception {
147165
try (Stream<Path> paths = Files.find(store, 2,
148166
(path, attributes) -> attributes.isRegularFile()

testsuites/benchmark-common/src/main/java/org/eclipse/rdf4j/benchmark/common/ThemeQueryCatalog.java

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
import java.util.List;
1616
import java.util.Map;
1717
import java.util.Objects;
18+
import java.util.OptionalLong;
1819
import java.util.stream.Collectors;
1920

2021
import org.eclipse.rdf4j.benchmark.rio.util.ThemeDataSetGenerator.Theme;
@@ -23,7 +24,10 @@ public final class ThemeQueryCatalog {
2324

2425
public static final int QUERY_COUNT = 13;
2526

27+
private static final long NO_EXPECTED_COUNT_BINDING_VALUE = -1L;
28+
2629
private static final Map<Theme, List<BenchmarkQuery>> QUERIES = new EnumMap<>(Theme.class);
30+
private static final Map<Theme, long[]> EXPECTED_COUNT_BINDING_VALUES = new EnumMap<>(Theme.class);
2731

2832
static {
2933
String medicalPrefix = String.join("\n",
@@ -1676,6 +1680,7 @@ public final class ThemeQueryCatalog {
16761680
"}"),
16771681
25710L)));
16781682

1683+
registerExpectedCountBindingValues();
16791684
validateQueries();
16801685
}
16811686

@@ -1709,6 +1714,15 @@ public static long expectedCountFor(Theme theme, int index) {
17091714
return benchmarkQueryFor(theme, index).getExpectedCount();
17101715
}
17111716

1717+
public static OptionalLong expectedCountBindingValueFor(Theme theme, int index) {
1718+
benchmarkQueryFor(theme, index);
1719+
long value = EXPECTED_COUNT_BINDING_VALUES.get(theme)[index];
1720+
if (value == NO_EXPECTED_COUNT_BINDING_VALUE) {
1721+
return OptionalLong.empty();
1722+
}
1723+
return OptionalLong.of(value);
1724+
}
1725+
17121726
private static void validateQueries() {
17131727
for (Theme theme : Theme.values()) {
17141728
List<BenchmarkQuery> queries = QUERIES.get(theme);
@@ -1718,9 +1732,52 @@ private static void validateQueries() {
17181732
if (queries.size() != QUERY_COUNT) {
17191733
throw new IllegalStateException("Theme " + theme + " has " + queries.size() + " queries");
17201734
}
1735+
long[] expectedCountBindingValues = EXPECTED_COUNT_BINDING_VALUES.get(theme);
1736+
if (expectedCountBindingValues == null) {
1737+
throw new IllegalStateException("Missing expected count binding values for theme " + theme);
1738+
}
1739+
if (expectedCountBindingValues.length != QUERY_COUNT) {
1740+
throw new IllegalStateException("Theme " + theme + " has " + expectedCountBindingValues.length
1741+
+ " expected count binding values");
1742+
}
1743+
for (int index = 0; index < expectedCountBindingValues.length; index++) {
1744+
long expectedCountBindingValue = expectedCountBindingValues[index];
1745+
if (expectedCountBindingValue < NO_EXPECTED_COUNT_BINDING_VALUE) {
1746+
throw new IllegalStateException("Theme " + theme + " query " + index
1747+
+ " has invalid expected count binding value " + expectedCountBindingValue);
1748+
}
1749+
if (expectedCountBindingValue != NO_EXPECTED_COUNT_BINDING_VALUE
1750+
&& !queries.get(index).getQuery().contains(" AS ?count)")) {
1751+
throw new IllegalStateException("Theme " + theme + " query " + index
1752+
+ " has an expected count binding value but does not project ?count");
1753+
}
1754+
}
17211755
}
17221756
}
17231757

1758+
private static void registerExpectedCountBindingValues() {
1759+
EXPECTED_COUNT_BINDING_VALUES.put(Theme.MEDICAL_RECORDS, expectedCountBindingValues(
1760+
7571, 0, -1, 8309, 24971, 0, -1, 0, -1, 16352, 8335, -1, -1));
1761+
EXPECTED_COUNT_BINDING_VALUES.put(Theme.SOCIAL_MEDIA, expectedCountBindingValues(
1762+
6, 2, 3, -1, 5, 480, -1, 5, 3, 11, 2, -1, -1));
1763+
EXPECTED_COUNT_BINDING_VALUES.put(Theme.LIBRARY, expectedCountBindingValues(
1764+
128853, 0, -1, 7958, 0, 217, -1, 77295, -1, 0, 4, -1, -1));
1765+
EXPECTED_COUNT_BINDING_VALUES.put(Theme.ENGINEERING, expectedCountBindingValues(
1766+
132672, 0, -1, 348, 2, 0, -1, 0, -1, 0, 2, -1, -1));
1767+
EXPECTED_COUNT_BINDING_VALUES.put(Theme.HIGHLY_CONNECTED, expectedCountBindingValues(
1768+
40251, 36767, -1, 39720, 770, 3279, -1, 32513, 119, -1, 59, -1, -1));
1769+
EXPECTED_COUNT_BINDING_VALUES.put(Theme.TRAIN, expectedCountBindingValues(
1770+
8268, 0, -1, 67380, 2, 24, -1, 1, 9, -1, 18788, -1, -1));
1771+
EXPECTED_COUNT_BINDING_VALUES.put(Theme.ELECTRICAL_GRID, expectedCountBindingValues(
1772+
7396, 0, -1, 59629, 5, 47, -1, 6, -1, 0, 0, -1, -1));
1773+
EXPECTED_COUNT_BINDING_VALUES.put(Theme.PHARMA, expectedCountBindingValues(
1774+
18, -1, -1, -1, 4972, 32, -1, 2885, -1, 13, -1, -1, -1));
1775+
}
1776+
1777+
private static long[] expectedCountBindingValues(long... values) {
1778+
return values;
1779+
}
1780+
17241781
private static List<BenchmarkQuery> queriesForTheme(Theme theme) {
17251782
Objects.requireNonNull(theme, "theme");
17261783
List<BenchmarkQuery> queries = QUERIES.get(theme);

testsuites/benchmark-common/src/test/java/org/eclipse/rdf4j/benchmark/common/ThemeQueryCatalogExpectedCountTest.java

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,4 +42,29 @@ void expectedCountsMatchCatalogValues() {
4242
}
4343
}
4444
}
45+
46+
@Test
47+
void expectedCountBindingValuesMatchCatalogValues() {
48+
Map<Theme, long[]> expectedCountBindingValues = Map.of(
49+
Theme.MEDICAL_RECORDS, new long[] { 7571, 0, -1, 8309, 24971, 0, -1, 0, -1, 16352, 8335,
50+
-1, -1 },
51+
Theme.SOCIAL_MEDIA, new long[] { 6, 2, 3, -1, 5, 480, -1, 5, 3, 11, 2, -1, -1 },
52+
Theme.LIBRARY, new long[] { 128853, 0, -1, 7958, 0, 217, -1, 77295, -1, 0, 4, -1, -1 },
53+
Theme.ENGINEERING, new long[] { 132672, 0, -1, 348, 2, 0, -1, 0, -1, 0, 2, -1, -1 },
54+
Theme.HIGHLY_CONNECTED, new long[] { 40251, 36767, -1, 39720, 770, 3279, -1, 32513, 119, -1,
55+
59, -1, -1 },
56+
Theme.TRAIN, new long[] { 8268, 0, -1, 67380, 2, 24, -1, 1, 9, -1, 18788, -1, -1 },
57+
Theme.ELECTRICAL_GRID, new long[] { 7396, 0, -1, 59629, 5, 47, -1, 6, -1, 0, 0, -1, -1 },
58+
Theme.PHARMA, new long[] { 18, -1, -1, -1, 4972, 32, -1, 2885, -1, 13, -1, -1, -1 }
59+
);
60+
61+
for (Map.Entry<Theme, long[]> entry : expectedCountBindingValues.entrySet()) {
62+
Theme theme = entry.getKey();
63+
long[] values = entry.getValue();
64+
for (int index = 0; index < values.length; index++) {
65+
assertEquals(values[index], ThemeQueryCatalog.expectedCountBindingValueFor(theme, index).orElse(-1),
66+
"Unexpected count binding value for theme " + theme + " and query index " + index);
67+
}
68+
}
69+
}
4570
}

0 commit comments

Comments
 (0)