From 782ffd84647a851607913dda7c8707963e1a5cd6 Mon Sep 17 00:00:00 2001 From: Jerven Bolleman Date: Tue, 8 Apr 2025 17:23:58 +0200 Subject: [PATCH 1/3] GH-5382 Create console command fix for config ns change, plus a test case. --- .../eclipse/rdf4j/console/command/Create.java | 72 ++++++++------ .../rdf4j/console/command/CreateTest.java | 93 +++++++++++++++++++ 2 files changed, 138 insertions(+), 27 deletions(-) create mode 100644 tools/console/src/test/java/org/eclipse/rdf4j/console/command/CreateTest.java diff --git a/tools/console/src/main/java/org/eclipse/rdf4j/console/command/Create.java b/tools/console/src/main/java/org/eclipse/rdf4j/console/command/Create.java index b4069a3a3f0..a47268835c1 100644 --- a/tools/console/src/main/java/org/eclipse/rdf4j/console/command/Create.java +++ b/tools/console/src/main/java/org/eclipse/rdf4j/console/command/Create.java @@ -28,6 +28,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Optional; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -40,6 +41,7 @@ import org.eclipse.rdf4j.model.impl.LinkedHashModel; import org.eclipse.rdf4j.model.impl.SimpleValueFactory; import org.eclipse.rdf4j.model.util.Models; +import org.eclipse.rdf4j.model.vocabulary.CONFIG; import org.eclipse.rdf4j.model.vocabulary.RDF; import org.eclipse.rdf4j.repository.RepositoryReadOnlyException; import org.eclipse.rdf4j.repository.config.ConfigTemplate; @@ -143,12 +145,12 @@ private String getUserTemplates() { */ private String getBuiltinTemplates() { // assume the templates are all located in the same jar "directory" as the RepositoryConfig class - Class cl = RepositoryConfig.class; + Class cl = RepositoryConfig.class; try { URI dir = cl.getResource(cl.getSimpleName() + ".class").toURI(); if (dir.getScheme().equals("jar")) { - try (FileSystem fs = FileSystems.newFileSystem(dir, Collections.EMPTY_MAP, null)) { + try (FileSystem fs = FileSystems.newFileSystem(dir, Collections.emptyMap(), null)) { // turn package structure into directory structure String pkg = cl.getPackage().getName().replaceAll("\\.", "/"); return getOrderedTemplates(fs.getPath(pkg)); @@ -187,15 +189,9 @@ private void createRepository(final String templateName) { boolean eof = inputParameters(valueMap, variableMap, configTemplate.getMultilineMap()); if (!eof) { final String configString = configTemplate.render(valueMap); - final Model graph = new LinkedHashModel(); + final Model graph = parseConfigIntoModel(configString); - final RDFParser rdfParser = Rio.createParser(RDFFormat.TURTLE, SimpleValueFactory.getInstance()); - rdfParser.setRDFHandler(new StatementCollector(graph)); - rdfParser.parse(new StringReader(configString), RepositoryConfigSchema.NAMESPACE); - - final Resource repositoryNode = Models - .subject(graph.getStatements(null, RDF.TYPE, RepositoryConfigSchema.REPOSITORY)) - .orElseThrow(() -> new RepositoryConfigException("missing repository node")); + final Resource repositoryNode = findRepositoryNode(graph); final RepositoryConfig repConfig = RepositoryConfig.create(graph, repositoryNode); repConfig.validate(); @@ -232,6 +228,25 @@ private void createRepository(final String templateName) { } } + private Resource findRepositoryNode(final Model graph) { + Optional modern = Models + .subject(graph.getStatements(null, RDF.TYPE, CONFIG.Rep.Repository)); + Resource repositoryNode = modern + .orElseGet(() -> Models + .subject(graph.getStatements(null, RDF.TYPE, RepositoryConfigSchema.REPOSITORY)) + .orElseThrow(() -> new RepositoryConfigException("missing repository node"))); + return repositoryNode; + } + + private Model parseConfigIntoModel(final String configString) throws IOException { + final Model graph = new LinkedHashModel(); + + final RDFParser rdfParser = Rio.createParser(RDFFormat.TURTLE, SimpleValueFactory.getInstance()); + rdfParser.setRDFHandler(new StatementCollector(graph)); + rdfParser.parse(new StringReader(configString), CONFIG.NAMESPACE); + return graph; + } + /** * Ask user to specify values for the template variables * @@ -251,23 +266,7 @@ private boolean inputParameters(final Map valueMap, final Map values = entry.getValue(); - StringBuilder sb = new StringBuilder(); - sb.append(var); - - if (values.size() > 1) { - sb.append(" ("); - for (int i = 0; i < values.size(); i++) { - if (i > 0) { - sb.append("|"); - } - sb.append(values.get(i)); - } - sb.append(")"); - } - if (!values.isEmpty()) { - sb.append(" [" + values.get(0) + "]"); - } - String prompt = sb.append(": ").toString(); + String prompt = buildPromptString(values, var); String value = multilineInput.containsKey(var) ? consoleIO.readMultiLineInput(prompt) : consoleIO.readln(prompt); eof = (value == null); @@ -284,6 +283,25 @@ private boolean inputParameters(final Map valueMap, final Map values, String var) { + StringBuilder sb = new StringBuilder(); + sb.append(var); + if (values.size() > 1) { + sb.append(" ("); + for (int i = 0; i < values.size(); i++) { + if (i > 0) { + sb.append("|"); + } + sb.append(values.get(i)); + } + sb.append(")"); + } + if (!values.isEmpty()) { + sb.append(" [" + values.get(0) + "]"); + } + return sb.append(": ").toString(); + } + /** * Create input stream from a template file in the specified file directory. If the file cannot be found, try to * read it from the embedded java resources instead. diff --git a/tools/console/src/test/java/org/eclipse/rdf4j/console/command/CreateTest.java b/tools/console/src/test/java/org/eclipse/rdf4j/console/command/CreateTest.java new file mode 100644 index 00000000000..182d94b186f --- /dev/null +++ b/tools/console/src/test/java/org/eclipse/rdf4j/console/command/CreateTest.java @@ -0,0 +1,93 @@ +/******************************************************************************* + * Copyright (c) 2018 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.console.command; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +import org.eclipse.rdf4j.common.exception.RDF4JException; +import org.eclipse.rdf4j.console.ConsoleIO; +import org.eclipse.rdf4j.repository.manager.LocalRepositoryManager; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +/** + * Test verify command + * + * @author Bart Hanssens + */ +public class CreateTest extends AbstractCommandTest { + private Create cmd; + private ConsoleIO io; + + @BeforeEach + public void setUp() throws IOException, RDF4JException { + InputStream input = mock(InputStream.class); + OutputStream out = mock(OutputStream.class); + when(mockConsoleState.getDataDirectory()).thenReturn(locationFile); + when(mockConsoleState.getManager()).thenReturn(new LocalRepositoryManager(locationFile)); + + io = new ConsoleIO(input, out, mockConsoleState) { + + @Override + public String readMultiLineInput(String promt) { + switch (promt) { + default: + return null; + } + } + + @Override + public String readMultiLineInput() { + return null; + } + + @Override + public String readln(String... message) { + switch (message[0]) { + case "Repository ID [memory]: ": + return "Y"; + case "Repository title [Memory store]: ": + return "Create-Test-Memory-Store"; + case "Query Iteration Cache sync threshold [10000]: ": + return "10"; + case "Persist (true|false) [true]: ": + return "false"; + case "Sync delay [0]: ": + return "0"; + case "Query Evaluation Mode (STRICT|STANDARD) [STRICT]: ": + return "STANDARD"; + } + return null; + } + + @Override + public boolean askProceed(String msg, boolean defaultValue) { + return true; + } + + }; + + cmd = new Create(io, mockConsoleState); + } + + @Test + public final void startCreate() { + cmd.execute("create", "memory"); + assertFalse(io.wasErrorWritten()); + } + +} From babd10b831e182a27daae3bae69d1e07c943a70c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ha=CC=8Avard=20Ottestad?= Date: Sat, 12 Apr 2025 13:01:25 +0200 Subject: [PATCH 2/3] GH-5294 add test --- .../memory/MemoryStoreConnectionTest.java | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/core/sail/memory/src/test/java/org/eclipse/rdf4j/sail/memory/MemoryStoreConnectionTest.java b/core/sail/memory/src/test/java/org/eclipse/rdf4j/sail/memory/MemoryStoreConnectionTest.java index ac85aff72aa..1c92e4a5c1a 100644 --- a/core/sail/memory/src/test/java/org/eclipse/rdf4j/sail/memory/MemoryStoreConnectionTest.java +++ b/core/sail/memory/src/test/java/org/eclipse/rdf4j/sail/memory/MemoryStoreConnectionTest.java @@ -10,15 +10,47 @@ *******************************************************************************/ package org.eclipse.rdf4j.sail.memory; +import static org.assertj.core.api.Assertions.assertThat; + import java.io.File; +import org.eclipse.rdf4j.common.transaction.IsolationLevels; +import org.eclipse.rdf4j.model.IRI; +import org.eclipse.rdf4j.model.vocabulary.RDF; import org.eclipse.rdf4j.repository.Repository; import org.eclipse.rdf4j.repository.sail.SailRepository; import org.eclipse.rdf4j.testsuite.repository.RepositoryConnectionTest; +import org.junit.jupiter.api.Test; public class MemoryStoreConnectionTest extends RepositoryConnectionTest { @Override protected Repository createRepository(File dataDir) { return new SailRepository(new MemoryStore()); } + + @Test + public void reallyBigUpdateToTriggerPotentialStackOverflowTest() { + setupTest(IsolationLevels.NONE); + + IRI g1 = vf.createIRI("urn:test:g1"); + testCon.begin(); + testCon.add(vf.createBNode(), RDF.TYPE, g1); + testCon.commit(); + + testCon.begin(); + + StringBuilder bigUpdate = new StringBuilder(); + bigUpdate.append("DELETE { ?s ?p ?o } INSERT{\n"); + + for (int i = 0; i < 20000; i++) { + bigUpdate.append(" [] \"").append(i).append("\".\n"); + } + bigUpdate.append("\n} WHERE { ?s ?p ?o }"); + + testCon.prepareUpdate(bigUpdate.toString()).execute(); + + long size = testCon.size(); + assertThat(size).isEqualTo(20000); + testCon.commit(); + } } From 5aea9366680492271870b375e20330f9f9553c05 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ha=CC=8Avard=20Ottestad?= Date: Sat, 12 Apr 2025 13:02:36 +0200 Subject: [PATCH 3/3] GH-5294 make the StatementPatternCollector less recursive --- .../collectors/StatementPatternCollector.java | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/core/queryalgebra/model/src/main/java/org/eclipse/rdf4j/query/algebra/helpers/collectors/StatementPatternCollector.java b/core/queryalgebra/model/src/main/java/org/eclipse/rdf4j/query/algebra/helpers/collectors/StatementPatternCollector.java index 29c34e5e037..5dac23d105e 100644 --- a/core/queryalgebra/model/src/main/java/org/eclipse/rdf4j/query/algebra/helpers/collectors/StatementPatternCollector.java +++ b/core/queryalgebra/model/src/main/java/org/eclipse/rdf4j/query/algebra/helpers/collectors/StatementPatternCollector.java @@ -15,8 +15,10 @@ import java.util.List; import org.eclipse.rdf4j.query.algebra.Filter; +import org.eclipse.rdf4j.query.algebra.Join; import org.eclipse.rdf4j.query.algebra.QueryModelNode; import org.eclipse.rdf4j.query.algebra.StatementPattern; +import org.eclipse.rdf4j.query.algebra.TupleExpr; import org.eclipse.rdf4j.query.algebra.helpers.AbstractSimpleQueryModelVisitor; /** @@ -46,6 +48,37 @@ public void meet(Filter node) { node.getArg().visit(this); } + @Override + public void meet(Join node) throws RuntimeException { + TupleExpr leftArg = node.getLeftArg(); + TupleExpr rightArg = node.getRightArg(); + + // INSERT clause is often a deeply nested join. Recursive approach may cause stack overflow. Attempt + // non-recursive (or at least less-recursive) approach first. + while (true) { + if (leftArg instanceof Join && !(rightArg instanceof Join)) { + rightArg.visit(this); + + Join join = (Join) leftArg; + leftArg = join.getLeftArg(); + rightArg = join.getRightArg(); + + } else if (rightArg instanceof Join && !(leftArg instanceof Join)) { + leftArg.visit(this); + + Join join = (Join) rightArg; + leftArg = join.getLeftArg(); + rightArg = join.getRightArg(); + + } else { + leftArg.visit(this); + rightArg.visit(this); + return; + } + } + + } + @Override public void meet(StatementPattern node) { statementPatterns.add(node);