diff --git a/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/PropertyShape.java b/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/PropertyShape.java index 3c87f74efd3..396c9893434 100644 --- a/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/PropertyShape.java +++ b/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/PropertyShape.java @@ -136,6 +136,11 @@ public ValidationQuery generateSparqlValidationQuery(ConnectionsGroup connection return ValidationQuery.Deactivated.getInstance(); } + if (!getPath().isSupported()) { + logger.error("Unsupported SHACL feature detected: {}. Shape ignored!\n{}", path, this); + return ValidationQuery.Deactivated.getInstance(); + } + ValidationQuery validationQuery = constraintComponents.stream() .map(c -> { ValidationQuery validationQuery1 = c.generateSparqlValidationQuery(connectionsGroup, @@ -177,38 +182,14 @@ public PlanNode generateTransactionalValidationPlan(ConnectionsGroup connections return EmptyNode.getInstance(); } - PlanNode union = EmptyNode.getInstance(); + if (!getPath().isSupported()) { + logger.error("Unsupported SHACL feature detected: {}. Shape ignored!\n{}", path, this); + return EmptyNode.getInstance(); + } -// if (negatePlan) { -// assert overrideTargetNode == null : "Negated property shape with override target is not supported at the moment!"; -// -// PlanNode ret = EmptyNode.getInstance(); -// -// for (ConstraintComponent constraintComponent : constraintComponents) { -// PlanNode planNode = constraintComponent.generateTransactionalValidationPlan(connectionsGroup, -// logValidationPlans, () -> getAllLocalTargetsPlan(connectionsGroup, negatePlan), negateChildren, -// false, Scope.propertyShape); -// -// PlanNode allTargetsPlan = getAllLocalTargetsPlan(connectionsGroup, negatePlan); -// -// Unique invalid = Unique.getInstance(planNode); -// -// PlanNode discardedLeft = new InnerJoin(allTargetsPlan, invalid) -// .getDiscardedLeft(BufferedPlanNode.class); -// -// ret = UnionNode.getInstance(ret, discardedLeft); -// -// } -// -// return ret; -// -// } + PlanNode union = EmptyNode.getInstance(); for (ConstraintComponent constraintComponent : constraintComponents) { - if (!getPath().isSupported()) { - logger.error("Unsupported path detected. Shape ignored!\n" + this); - continue; - } PlanNode validationPlanNode = constraintComponent .generateTransactionalValidationPlan(connectionsGroup, validationSettings, overrideTargetNode, @@ -276,6 +257,17 @@ public Path getPath() { return path; } + @Override + public boolean requiresEvaluation(ConnectionsGroup connectionsGroup, Scope scope, Resource[] dataGraph, + StatementMatcher.StableRandomVariableProvider stableRandomVariableProvider) { + if (!getPath().isSupported()) { + logger.error("Unsupported SHACL feature detected: {}. Shape ignored!\n{}", path, this); + return false; + } + + return super.requiresEvaluation(connectionsGroup, scope, dataGraph, stableRandomVariableProvider); + } + @Override public ConstraintComponent deepClone() { PropertyShape nodeShape = new PropertyShape(this); diff --git a/core/sail/shacl/src/test/java/org/eclipse/rdf4j/sail/shacl/AbstractShaclTest.java b/core/sail/shacl/src/test/java/org/eclipse/rdf4j/sail/shacl/AbstractShaclTest.java index a9e3a37d1ba..3acb20fd4b6 100644 --- a/core/sail/shacl/src/test/java/org/eclipse/rdf4j/sail/shacl/AbstractShaclTest.java +++ b/core/sail/shacl/src/test/java/org/eclipse/rdf4j/sail/shacl/AbstractShaclTest.java @@ -333,8 +333,7 @@ public String toString() { private static Stream findTestCases(String testCase, ExpectedResult baseCase) { String shacl; - try (InputStream resourceAsStream = AbstractShaclTest.class.getClassLoader() - .getResourceAsStream(testCase + "/shacl.trig")) { + try (InputStream resourceAsStream = getResourceAsStream(testCase + "/shacl.trig")) { assert Objects.nonNull(resourceAsStream) : "Could not find: " + testCase + "/shacl.trig"; shacl = IOUtils.toString(resourceAsStream, StandardCharsets.UTF_8); @@ -347,7 +346,7 @@ private static Stream findTestCases(String testCase, ExpectedResult ba return Stream.empty(); } - return Arrays.stream(new File(resource.getFile()).list()) + return Arrays.stream(Objects.requireNonNull(new File(resource.getFile()).list())) .filter(s -> !s.startsWith(".")) .sorted() .map(caseName -> { @@ -443,8 +442,6 @@ void runTestCase(TestCase testCase, IsolationLevel isolationLevel, boolean prelo } - Model validationReportActual = new LinkedHashModel(); - for (File queryFile : testCase.getQueries()) { try { String query = FileUtils.readFileToString(queryFile, StandardCharsets.UTF_8); @@ -466,8 +463,6 @@ void runTestCase(TestCase testCase, IsolationLevel isolationLevel, boolean prelo exception = true; logger.debug(sailException.getMessage()); - validationReportActual = ((ShaclSailValidationException) sailException.getCause()) - .validationReportAsModel(); printResults(sailException); } } catch (IOException e) { @@ -506,17 +501,18 @@ private void printTestCase(TestCase testCase) { private static void testValidationReport(String dataPath, Model validationReportActual) { try { - InputStream resourceAsStream = AbstractShaclTest.class.getClassLoader() - .getResourceAsStream(dataPath + "report.ttl"); + InputStream resourceAsStream = getResourceAsStream(dataPath + "report.ttl"); if (resourceAsStream == null) { - logger.error(dataPath + "report.ttl did not exist. Creating an empty file!"); + logger.warn(dataPath + "report.ttl did not exist, attempting to create an empty file!"); String file = Objects.requireNonNull(AbstractShaclTest.class.getClassLoader() .getResource(dataPath)) .getFile() .replace("/target/test-classes/", "/src/test/resources/"); boolean newFile = new File(file + "report.ttl").createNewFile(); - + if (!newFile) { + logger.error(dataPath + "report.ttl did not exist and could not create an empty file!"); + } } Model validationReportExpected = getModel(resourceAsStream); @@ -533,6 +529,10 @@ private static void testValidationReport(String dataPath, Model validationReport } } + private static InputStream getResourceAsStream(String dataPath) { + return AbstractShaclTest.class.getClassLoader().getResourceAsStream(dataPath); + } + private static void writeActualModelToExpectedModelForDevPurposes(String dataPath, Model report) throws IOException { String file = Objects.requireNonNull(AbstractShaclTest.class.getClassLoader() @@ -631,8 +631,7 @@ void referenceImplementationTestCaseValidation(TestCase testCase) { org.apache.jena.rdf.model.Model data = JenaUtil.createMemoryModel(); if (testCase.hasInitialData()) { - try (InputStream resourceAsStream = AbstractShaclTest.class.getClassLoader() - .getResourceAsStream(testCase.getInitialData())) { + try (InputStream resourceAsStream = getResourceAsStream(testCase.getInitialData())) { data.read(resourceAsStream, "", org.apache.jena.util.FileUtils.langTurtle); } catch (IOException e) { throw new IllegalStateException(e); @@ -668,8 +667,7 @@ void referenceImplementationTestCaseValidation(TestCase testCase) { RDFFormat.TRIG); try { - InputStream resourceAsStream = AbstractShaclTest.class.getClassLoader() - .getResourceAsStream(testCase.getTestCasePath() + "report.ttl"); + InputStream resourceAsStream = getResourceAsStream(testCase.getTestCasePath() + "report.ttl"); Model validationReportActual = getModel(resourceAsStream); @@ -717,8 +715,7 @@ private static Model getModel(InputStream resourceAsStream) throws IOException { private static void checkShapesConformToW3cShaclRecommendation(org.apache.jena.rdf.model.Model shacl) { org.apache.jena.rdf.model.Model w3cShacl = JenaUtil.createMemoryModel(); - try (InputStream resourceAsStream = AbstractShaclTest.class.getClassLoader() - .getResourceAsStream("w3cshacl.ttl")) { + try (InputStream resourceAsStream = getResourceAsStream("w3cshacl.ttl")) { w3cShacl.read(resourceAsStream, "", org.apache.jena.util.FileUtils.langTurtle); } catch (IOException e) { throw new IllegalStateException(e); @@ -870,7 +867,7 @@ private void printFile(String filename) { try { System.out.println("### " + filename + " ###"); String s = IOUtils.toString( - Objects.requireNonNull(AbstractShaclTest.class.getClassLoader().getResourceAsStream(filename)), + Objects.requireNonNull(getResourceAsStream(filename)), StandardCharsets.UTF_8); s = removeLeadingPrefixStatements(s); @@ -883,29 +880,30 @@ private void printFile(String filename) { } private static String removeLeadingPrefixStatements(String s) { - String[] split = s.split("\n"); - s = ""; + String[] splitByNewLine = s.split("\n"); + boolean skippingPrefixes = true; - for (String s1 : split) { + StringBuilder stringBuilder = new StringBuilder(); + for (String line : splitByNewLine) { if (skippingPrefixes) { - if (!(s1.trim().equals("") || - s1.trim().toLowerCase().startsWith("@prefix") || - s1.trim().toLowerCase().startsWith("@base") || - s1.trim().toLowerCase().startsWith("prefix"))) { + if (!(line.trim().equals("") || + line.trim().toLowerCase().startsWith("@prefix") || + line.trim().toLowerCase().startsWith("@base") || + line.trim().toLowerCase().startsWith("prefix"))) { skippingPrefixes = false; } } if (!skippingPrefixes) { - s += s1 + "\n"; + stringBuilder.append(line).append("\n"); } } - return s; + return stringBuilder.toString(); } - void runTestCaseSingleTransaction(TestCase testCase, IsolationLevel isolationLevel) { + void runTestCaseSingleTransaction(TestCase testCase) { SailRepository shaclRepository = getShaclSail(testCase, true); @@ -915,7 +913,7 @@ void runTestCaseSingleTransaction(TestCase testCase, IsolationLevel isolationLev Model validationReportActual = new LinkedHashModel(); try (SailRepositoryConnection shaclSailConnection = shaclRepository.getConnection()) { - shaclSailConnection.begin(isolationLevel); + shaclSailConnection.begin(IsolationLevels.NONE); for (File queryFile : testCase.getQueries()) { try { @@ -1028,7 +1026,6 @@ void runParsingTest(TestCase testCase) { .getDataAndRelease(); Model actual = new DynamicModelFactory().createEmptyModel(); - HashSet dedupe = new HashSet<>(); shapes.forEach(shape -> shape.toModel(actual)); Model expected = new LinkedHashModel(testCase.getShacl()); @@ -1093,8 +1090,8 @@ private void printResults(ValidationReport report) { } private void printResults(RepositoryException sailException) { - ValidationReport validationReport = ((ShaclSailValidationException) sailException.getCause()) - .getValidationReport(); + var shaclSailValidationException = (ShaclSailValidationException) sailException.getCause(); + ValidationReport validationReport = shaclSailValidationException.getValidationReport(); printResults(validationReport); } @@ -1159,27 +1156,6 @@ void runWithAutomaticLogging(Runnable r) { } } - /** - * Sort and output testCasePaths - * - * @param args - */ - public static void main(String[] args) { - - System.out.println("\n\tprivate static final List testCasePaths = Stream.of("); - String testCasesString = testCasePaths - .stream() - .map(a -> "\t\t\"" + a + "\"") - .reduce((a, b) -> a + ",\n" + b) - .orElse(""); - - System.out.println(testCasesString); - System.out.println("\t)\n" + - "\t\t.distinct()\n" + - "\t\t.sorted()\n" + - "\t\t.collect(Collectors.toList());"); - } - enum ExpectedResult { valid, invalid diff --git a/core/sail/shacl/src/test/java/org/eclipse/rdf4j/sail/shacl/ShaclTest.java b/core/sail/shacl/src/test/java/org/eclipse/rdf4j/sail/shacl/ShaclTest.java index 1c9c9838f10..e9bdeed3463 100644 --- a/core/sail/shacl/src/test/java/org/eclipse/rdf4j/sail/shacl/ShaclTest.java +++ b/core/sail/shacl/src/test/java/org/eclipse/rdf4j/sail/shacl/ShaclTest.java @@ -12,7 +12,6 @@ package org.eclipse.rdf4j.sail.shacl; import org.eclipse.rdf4j.common.transaction.IsolationLevel; -import org.eclipse.rdf4j.common.transaction.IsolationLevels; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.MethodSource; @@ -30,7 +29,7 @@ public void test(TestCase testCase, IsolationLevel isolationLevel) { @ParameterizedTest @MethodSource("testCases") public void testSingleTransaction(TestCase testCase) { - runWithAutomaticLogging(() -> runTestCaseSingleTransaction(testCase, IsolationLevels.NONE)); + runWithAutomaticLogging(() -> runTestCaseSingleTransaction(testCase)); } @ParameterizedTest diff --git a/core/sail/shacl/src/test/java/org/eclipse/rdf4j/sail/shacl/UnknownShapesTest.java b/core/sail/shacl/src/test/java/org/eclipse/rdf4j/sail/shacl/UnknownShapesTest.java index f4e782f5f50..0182f886314 100644 --- a/core/sail/shacl/src/test/java/org/eclipse/rdf4j/sail/shacl/UnknownShapesTest.java +++ b/core/sail/shacl/src/test/java/org/eclipse/rdf4j/sail/shacl/UnknownShapesTest.java @@ -12,34 +12,37 @@ import java.io.IOException; import java.util.ArrayList; -import java.util.Arrays; -import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.stream.Collectors; +import org.eclipse.rdf4j.model.util.Values; import org.eclipse.rdf4j.model.vocabulary.RDF; import org.eclipse.rdf4j.model.vocabulary.RDFS; import org.eclipse.rdf4j.repository.sail.SailRepository; import org.eclipse.rdf4j.repository.sail.SailRepositoryConnection; import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.slf4j.LoggerFactory; +import ch.qos.logback.classic.Logger; import ch.qos.logback.classic.spi.ILoggingEvent; import ch.qos.logback.core.AppenderBase; public class UnknownShapesTest { + private TestAppender appender; - @Test - public void testPropertyShapes() throws IOException, InterruptedException { - ch.qos.logback.classic.Logger root = (ch.qos.logback.classic.Logger) LoggerFactory - .getLogger(ch.qos.logback.classic.Logger.ROOT_LOGGER_NAME); + @BeforeEach + public void beforeEach() { + appender = new TestAppender(); - MyAppender newAppender = new MyAppender(); - root.addAppender(newAppender); + Logger root = (Logger) LoggerFactory.getLogger(Logger.ROOT_LOGGER_NAME); + root.addAppender(appender); + } + @Test + public void testPropertyShapes() throws IOException, InterruptedException { SailRepository shaclRepository = Utils.getInitializedShaclRepository("unknownProperties.trig"); try (SailRepositoryConnection connection = shaclRepository.getConnection()) { @@ -50,7 +53,7 @@ public void testPropertyShapes() throws IOException, InterruptedException { Thread.sleep(100); - List relevantLog = newAppender.logged.stream() + List relevantLog = appender.logged.stream() .filter(m -> m.startsWith("Unsupported SHACL feature")) .distinct() .sorted(String::compareTo) @@ -67,15 +70,8 @@ public void testPropertyShapes() throws IOException, InterruptedException { } - @Disabled @Test public void testComplexPath() throws IOException, InterruptedException { - ch.qos.logback.classic.Logger root = (ch.qos.logback.classic.Logger) LoggerFactory - .getLogger(ch.qos.logback.classic.Logger.ROOT_LOGGER_NAME); - - MyAppender newAppender = new MyAppender(); - root.addAppender(newAppender); - SailRepository shaclRepository = Utils.getInitializedShaclRepository("complexPath.trig"); try (SailRepositoryConnection connection = shaclRepository.getConnection()) { @@ -86,25 +82,55 @@ public void testComplexPath() throws IOException, InterruptedException { Thread.sleep(100); - Set relevantLog = newAppender.logged.stream() + Set relevantLog = appender.logged.stream() .filter(m -> m.startsWith("Unsupported SHACL feature")) .map(s -> s.replaceAll("\r\n|\r|\n", " ")) .map(String::trim) .collect(Collectors.toSet()); - Set expected = new HashSet<>(Arrays.asList( - "Unsupported SHACL feature with complex path. Only single predicate paths, or single predicate inverse paths are supported. shape has been deactivated! @prefix sh: . sh:path [ sh:alternativePath ] .", - "Unsupported SHACL feature with complex path. Only single predicate paths, or single predicate inverse paths are supported. shape has been deactivated! @prefix sh: . sh:path ( ) .", - "Unsupported SHACL feature with complex path. Only single predicate paths, or single predicate inverse paths are supported. shape has been deactivated! @prefix sh: . sh:path [ sh:inversePath [ sh:zeroOrMorePath ] ] .")); + Set expected = Set.of( + "Unsupported SHACL feature detected: InversePath{ ZeroOrMorePath{ SimplePath{ } } }. Shape ignored! a sh:PropertyShape; sh:path [ sh:inversePath [ sh:zeroOrMorePath ] ]; sh:minCount 1 .", + "Unsupported SHACL feature detected: InversePath{ ZeroOrMorePath{ SimplePath{ } } }. Shape ignored! a sh:PropertyShape; sh:path [ sh:inversePath [ sh:zeroOrMorePath ] ]; sh:datatype xsd:int .", + "Unsupported SHACL feature detected: AlternativePath{ SimplePath{ } }. Shape ignored! a sh:PropertyShape; sh:path [ sh:alternativePath ]; sh:minCount 1 .", + "Unsupported SHACL feature detected: SequencePath{ [SimplePath{ }, SimplePath{ }] }. Shape ignored! a sh:PropertyShape; sh:path ( ); sh:minCount 1 ." + ); Assertions.assertEquals(expected, relevantLog); shaclRepository.shutDown(); } - static class MyAppender extends AppenderBase { + @Test + public void testThatUnknownPathsAreIgnored() throws IOException { + SailRepository shaclRepository = Utils.getInitializedShaclRepository("complexPath.trig"); + + // trigger SPARQL based validation + try (SailRepositoryConnection connection = shaclRepository.getConnection()) { + connection.begin(); + connection.add(RDF.TYPE, RDF.TYPE, RDFS.RESOURCE); + connection.commit(); + } + + // trigger transactional validation + try (SailRepositoryConnection connection = shaclRepository.getConnection()) { + connection.begin(); + connection.add(RDFS.RESOURCE, RDF.TYPE, RDFS.RESOURCE); + connection.commit(); + } + + // trigger requiresEvaluation(...) check + try (SailRepositoryConnection connection = shaclRepository.getConnection()) { + connection.begin(); + connection.add(RDFS.RESOURCE, Values.iri("http://example.com/ns#parent"), RDFS.RESOURCE); + connection.commit(); + } + + shaclRepository.shutDown(); + } + + private static class TestAppender extends AppenderBase { - List logged = new ArrayList<>(); + private final List logged = new ArrayList<>(); @Override public synchronized void doAppend(ILoggingEvent eventObject) { diff --git a/core/sail/shacl/src/test/resources/complexPath.trig b/core/sail/shacl/src/test/resources/complexPath.trig index dc89c7784f0..659853666d8 100644 --- a/core/sail/shacl/src/test/resources/complexPath.trig +++ b/core/sail/shacl/src/test/resources/complexPath.trig @@ -14,10 +14,11 @@ ex:PersonShape sh:targetClass rdfs:Resource ; rdfs:label "label" ; rdfs:subClassOf ex:HumanShape ; - sh:property ex:inverseOfWithComplex, ex:pathSequence, ex:alternativePath . + sh:property ex:inverseOfWithComplex, ex:pathSequence, ex:alternativePath, ex:nestedSequencePath . ex:inverseOfWithComplex sh:path [ sh:inversePath [sh:zeroOrMorePath ex:inverseThis] ] ; + sh:datatype xsd:int ; sh:minCount 1 . @@ -25,6 +26,12 @@ ex:pathSequence sh:path ( ex:parent ex:firstName ) ; sh:minCount 1 . +ex:nestedSequencePath + sh:path ex:parent ; + sh:maxCount 1 ; + sh:property ex:pathSequence ; + . + ex:alternativePath sh:path [ sh:alternativePath ex:father ] ; sh:minCount 1 . diff --git a/pom.xml b/pom.xml index 91a3d90143b..69a996a4aa5 100644 --- a/pom.xml +++ b/pom.xml @@ -401,7 +401,7 @@ org.apache.commons commons-text - 1.9 + 1.10.0 org.apache.commons diff --git a/site/content/download.md b/site/content/download.md index c9c0a513a71..2a8e3d0eaf1 100644 --- a/site/content/download.md +++ b/site/content/download.md @@ -5,15 +5,15 @@ toc: true You can either retrieve RDF4J via Apache Maven, or download the SDK or onejar directly. -## RDF4J 4.1.3 (latest) +## RDF4J 4.2.0 (latest) -RDF4J 4.1.3 is our latest stable release. It requires Java 11 minimally. -For details on what’s new and how to upgrade, see the [release and upgrade notes](/release-notes/4.1.3). +RDF4J 4.2.0 is our latest stable release. It requires Java 11 minimally. +For details on what’s new and how to upgrade, see the [release and upgrade notes](/release-notes/4.2.0). -- [RDF4J 4.1.3 SDK (zip)](http://www.eclipse.org/downloads/download.php?file=/rdf4j/eclipse-rdf4j-4.1.3-sdk.zip)
+- [RDF4J 4.2.0 SDK (zip)](http://www.eclipse.org/downloads/download.php?file=/rdf4j/eclipse-rdf4j-4.2.0-sdk.zip)
Full Eclipse RDF4J SDK, containing all libraries, RDF4J Server, Workbench, and Console applications, and Javadoc API. -- [RDF4J 4.1.3 onejar](http://www.eclipse.org/downloads/download.php?file=/rdf4j/eclipse-rdf4j-4.1.3-onejar.jar)
+- [RDF4J 4.2.0 onejar](http://www.eclipse.org/downloads/download.php?file=/rdf4j/eclipse-rdf4j-4.2.0-onejar.jar)
Single jar file for easy inclusion of the full RDF4J toolkit in your Java project. - [RDF4J artifacts](https://search.maven.org/search?q=org.eclipse.rdf4j) on the [Maven Central Repository](http://search.maven.org/) @@ -28,7 +28,7 @@ You can include RDF4J as a Maven dependency in your Java project by including th org.eclipse.rdf4j rdf4j-bom - 4.1.3 + 4.2.0 pom import @@ -53,6 +53,12 @@ which artifacts RDF4J provides. ## Older releases +### RDF4J 4.1 + +- [RDF4J 4.1.3 SDK (zip)](http://www.eclipse.org/downloads/download.php?file=/rdf4j/eclipse-rdf4j-4.1.3-sdk.zip) +- [RDF4J 4.1.3 onejar](http://www.eclipse.org/downloads/download.php?file=/rdf4j/eclipse-rdf4j-4.1.3-onejar.jar) + + ### RDF4J 4.0 - [RDF4J 4.0.4 SDK (zip)](http://www.eclipse.org/downloads/download.php?file=/rdf4j/eclipse-rdf4j-4.0.4-sdk.zip) diff --git a/site/content/news/rdf4j-420.md b/site/content/news/rdf4j-420.md new file mode 100644 index 00000000000..9564413c652 --- /dev/null +++ b/site/content/news/rdf4j-420.md @@ -0,0 +1,14 @@ +--- +title: "RDF4J 4.2.0 released" +date: 2022-09-16T21:19:19+0200 +layout: "single" +categories: ["news"] +--- +RDF4J 4.2.0 is now available. This is a minor release with performance improvements, bug fixes and a new feature to allow for custom SPARQL aggregate functions together with implementations for standard deviation and variance. + +For more details, have a look at the [release notes](/release-notes/4.2.0). + +### Links + +- [Download RDF4J](/download/) +- [release notes](/release-notes/4.2.0). diff --git a/site/content/release-notes/4.2.0.md b/site/content/release-notes/4.2.0.md new file mode 100644 index 00000000000..3d6bcb28e0d --- /dev/null +++ b/site/content/release-notes/4.2.0.md @@ -0,0 +1,32 @@ +--- +title: "4.2.0" +toc: true +--- +RDF4J 4.2.0 is a minor release introducing support for custom SPARQL aggregate functions as well as several performance improvements and bug fixes. + +### Aggregate functions +The support for custom SPARQL aggregate functions has allowed us to implement functions for standard deviation and variance using [Apache Commons Math3](https://commons.apache.org/proper/commons-math/javadocs/api-3.6.1/org/apache/commons/math3/stat/descriptive/SummaryStatistics.html). + +| IRI | Description | +|------------------------------------------------|--------------------------------------------------| +| http://rdf4j.org/aggregate#stdev | Standard deviation using sampling. | +| http://rdf4j.org/aggregate#stdev_population | Standard deviation using statistical population. | +| http://rdf4j.org/aggregate#variance | Variance using sampling. | +| http://rdf4j.org/aggregate#variance_population | Variance using statistical population. | + + +**Query example** +``` +SELECT ((?o) AS ?stdev) WHERE { + ?s ?p ?o . +} +``` + +### Performance +More accurate query statistics for the LMDB Store drastically improves query performance. The LMDB Store can already scale to considerably larger datasets than the NativeStore and now also matches the performance for smaller datasets. + +Validation performance for small transactions on large databases has been improved by batching together more operations when analyzing the changes in a transaction. + + +For a complete overview, see [all issues fixed in 4.2.0](https://github.com/eclipse/rdf4j/milestone/85?closed=1). + diff --git a/site/static/javadoc/4.2.0.tgz b/site/static/javadoc/4.2.0.tgz new file mode 100644 index 00000000000..b84b2d5e92f Binary files /dev/null and b/site/static/javadoc/4.2.0.tgz differ diff --git a/site/static/javadoc/latest.tgz b/site/static/javadoc/latest.tgz index 94fd27d1596..b84b2d5e92f 100644 Binary files a/site/static/javadoc/latest.tgz and b/site/static/javadoc/latest.tgz differ