Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@
<maven-install-plugin.version>3.1.4</maven-install-plugin.version>
<maven-site-plugin.version>3.7.1</maven-site-plugin.version>
<maven.compiler.release>${java.version}</maven.compiler.release>
<neo4j-java-driver.version>5.28.9</neo4j-java-driver.version>
<neo4j-java-driver.version>6.0.0</neo4j-java-driver.version>
<neo4j-migrations.version>2.17.3</neo4j-migrations.version>
<neo4j.version>5.26.12</neo4j.version>
<objenesis.version>3.0.1</objenesis.version>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
*/
package org.springframework.data.neo4j.core;

import java.util.List;
import java.util.function.Consumer;
import java.util.function.Predicate;
import java.util.regex.Pattern;
Expand All @@ -23,10 +24,9 @@

import org.apache.commons.logging.LogFactory;
import org.jspecify.annotations.Nullable;
import org.neo4j.driver.NotificationCategory;
import org.neo4j.driver.NotificationClassification;
import org.neo4j.driver.NotificationSeverity;
import org.neo4j.driver.summary.InputPosition;
import org.neo4j.driver.summary.Notification;
import org.neo4j.driver.summary.GqlNotification;
import org.neo4j.driver.summary.Plan;
import org.neo4j.driver.summary.ResultSummary;

Expand Down Expand Up @@ -66,8 +66,15 @@ final class ResultSummaries {
private static final LogAccessor cypherTopologyNotificationLog = new LogAccessor(
LogFactory.getLog("org.springframework.data.neo4j.cypher.topology"));

private static final Pattern DEPRECATED_ID_PATTERN = Pattern
.compile("(?im)The query used a deprecated function[.:] \\(?[`']id.+");
private static final LogAccessor cypherSchemaNotificationLog = new LogAccessor(
LogFactory.getLog("org.springframework.data.neo4j.cypher.schema"));

private static final List<Pattern> STUFF_THAT_MIGHT_INFORM_THAT_THE_ID_FUNCTION_IS_PROBLEMATIC = Stream.of(
"(?im)The query used a deprecated function[.:] \\(?[`']id.+",
"(?im).*id is deprecated and will be removed without a replacement\\.",
"(?im).*feature deprecated with replacement\\. id is deprecated\\. It is replaced by elementId or consider using an application-generated id\\.")
.map(Pattern::compile)
.toList();

private ResultSummaries() {
}
Expand All @@ -86,29 +93,33 @@ static ResultSummary process(ResultSummary resultSummary) {

private static void logNotifications(ResultSummary resultSummary) {

if (resultSummary.notifications().isEmpty() || !Neo4jClient.cypherLog.isWarnEnabled()) {
if (resultSummary.gqlStatusObjects().isEmpty() || !Neo4jClient.cypherLog.isWarnEnabled()) {
return;
}

boolean supressIdDeprecations = Neo4jClient.SUPPRESS_ID_DEPRECATIONS.getAcquire();
Predicate<Notification> isDeprecationWarningForId;
Predicate<GqlNotification> isDeprecationWarningForId;
try {
isDeprecationWarningForId = notification -> supressIdDeprecations
&& notification.category()
.orElse(NotificationCategory.UNRECOGNIZED)
.equals(NotificationCategory.DEPRECATION)
&& DEPRECATED_ID_PATTERN.matcher(notification.description()).matches();
&& notification.classification()
.filter(cat -> cat == NotificationClassification.UNRECOGNIZED
|| cat == NotificationClassification.DEPRECATION)
.isPresent()
&& STUFF_THAT_MIGHT_INFORM_THAT_THE_ID_FUNCTION_IS_PROBLEMATIC.stream()
.anyMatch(p -> p.matcher(notification.statusDescription()).matches());
}
finally {
Neo4jClient.SUPPRESS_ID_DEPRECATIONS.setRelease(supressIdDeprecations);
}

String query = resultSummary.query().text();
resultSummary.notifications()
resultSummary.gqlStatusObjects()
.stream()
.filter(GqlNotification.class::isInstance)
.map(GqlNotification.class::cast)
.filter(Predicate.not(isDeprecationWarningForId))
.forEach(notification -> notification.severityLevel().ifPresent(severityLevel -> {
var category = notification.category().orElse(null);
.forEach(notification -> notification.severity().ifPresent(severityLevel -> {
var category = notification.classification().orElse(null);

var logger = getLogAccessor(category);
Consumer<String> logFunction;
Expand All @@ -130,35 +141,22 @@ else if (severityLevel == NotificationSeverity.OFF) {
}));
}

private static LogAccessor getLogAccessor(@Nullable NotificationCategory category) {
if (category == null) {
private static LogAccessor getLogAccessor(@Nullable NotificationClassification classification) {
if (classification == null) {
return Neo4jClient.cypherLog;
}
if (category.equals(NotificationCategory.HINT)) {
return cypherHintNotificationLog;
}
else if (category.equals(NotificationCategory.DEPRECATION)) {
return cypherDeprecationNotificationLog;
}
else if (category.equals(NotificationCategory.PERFORMANCE)) {
return cypherPerformanceNotificationLog;
}
else if (category.equals(NotificationCategory.GENERIC)) {
return cypherGenericNotificationLog;
}
else if (category.equals(NotificationCategory.UNSUPPORTED)) {
return cypherUnsupportedNotificationLog;
}
else if (category.equals(NotificationCategory.UNRECOGNIZED)) {
return cypherUnrecognizedNotificationLog;
}
else if (category.equals(NotificationCategory.SECURITY)) {
return cypherSecurityNotificationLog;
}
else if (category.equals(NotificationCategory.TOPOLOGY)) {
return cypherTopologyNotificationLog;
}
return Neo4jClient.cypherLog;

return switch (classification) {
case HINT -> cypherHintNotificationLog;
case UNRECOGNIZED -> cypherUnrecognizedNotificationLog;
case UNSUPPORTED -> cypherUnsupportedNotificationLog;
case PERFORMANCE -> cypherPerformanceNotificationLog;
case DEPRECATION -> cypherDeprecationNotificationLog;
case SECURITY -> cypherSecurityNotificationLog;
case TOPOLOGY -> cypherTopologyNotificationLog;
case GENERIC -> cypherGenericNotificationLog;
case SCHEMA -> cypherSchemaNotificationLog;
};
}

/**
Expand All @@ -167,25 +165,23 @@ else if (category.equals(NotificationCategory.TOPOLOGY)) {
* @param forQuery the query that caused the notification
* @return a formatted string
*/
static String format(Notification notification, String forQuery) {
static String format(GqlNotification notification, String forQuery) {

InputPosition position = notification.position();
boolean hasPosition = position != null;
var position = notification.position().orElse(null);

StringBuilder queryHint = new StringBuilder();
String[] lines = forQuery.split("(\r\n|\n)");
for (int i = 0; i < lines.length; i++) {
String line = lines[i];
queryHint.append("\t").append(line).append(LINE_SEPARATOR);
if (hasPosition && i + 1 == position.line()) {
if (position != null && i + 1 == position.line()) {
queryHint.append("\t")
.append(Stream.generate(() -> " ").limit(position.column() - 1).collect(Collectors.joining()))
.append("^")
.append(System.lineSeparator());
}
}
return String.format("%s: %s%n%s%s", notification.code(), notification.title(), queryHint,
notification.description());
return String.format("%s (%s):%n%s", notification.statusDescription(), notification.gqlStatus(), queryHint);
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@
import java.util.function.LongSupplier;
import java.util.function.Supplier;
import java.util.function.UnaryOperator;
import java.util.stream.Collectors;

import org.jspecify.annotations.Nullable;
import org.neo4j.driver.types.MapAccessor;
Expand Down Expand Up @@ -166,7 +165,7 @@ else if (geoNearQuery) {
rawResult = newGeoResults(rawResult);
}
else if (this.queryMethod.isSearchQuery()) {
rawResult = createSearchResult((List<?>) rawResult, returnedType.getReturnedType());
rawResult = createSearchResult((List<?>) rawResult);
}

return resultProcessor.processResult(rawResult, preparingConverter);
Expand Down Expand Up @@ -207,11 +206,9 @@ private Slice<?> createSlice(boolean incrementLimit, Neo4jParameterAccessor para
}
}

private <T> SearchResults<?> createSearchResult(List<?> rawResult, Class<T> returnedType) {
List<SearchResult<T>> searchResults = rawResult.stream()
.map(rawValue -> (SearchResult<T>) rawValue)
.collect(Collectors.toUnmodifiableList());
return new SearchResults<>(searchResults);
@SuppressWarnings("unchecked")
private <T> SearchResults<?> createSearchResult(List<?> rawResult) {
return new SearchResults<>(rawResult.stream().map(rawValue -> (SearchResult<T>) rawValue).toList());
}

protected abstract <T> PreparedQuery<T> prepareQuery(Class<T> returnedType,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,7 @@ void databaseSelectionShouldWorkBeforeAsUser() {
verify(this.session).run(eq(cypher), MockitoHamcrest.argThat(new MapAssertionMatcher(expectedParameters)));
verify(this.result).stream();
verify(this.result).consume();
verify(this.resultSummary).notifications();
verify(this.resultSummary).gqlStatusObjects();
verify(this.resultSummary).hasPlan();
verify(this.record1).asMap();
verify(this.session).close();
Expand Down Expand Up @@ -181,7 +181,7 @@ void databaseSelectionShouldWorkAfterAsUser() {
verify(this.session).run(eq(cypher), MockitoHamcrest.argThat(new MapAssertionMatcher(expectedParameters)));
verify(this.result).stream();
verify(this.result).consume();
verify(this.resultSummary).notifications();
verify(this.resultSummary).gqlStatusObjects();
verify(this.resultSummary).hasPlan();
verify(this.record1).asMap();
verify(this.session).close();
Expand Down Expand Up @@ -220,7 +220,7 @@ void userSelectionShouldWork() {
verify(this.session).run(eq(cypher), MockitoHamcrest.argThat(new MapAssertionMatcher(expectedParameters)));
verify(this.result).stream();
verify(this.result).consume();
verify(this.resultSummary).notifications();
verify(this.resultSummary).gqlStatusObjects();
verify(this.resultSummary).hasPlan();
verify(this.record1).asMap();
verify(this.session).close();
Expand Down Expand Up @@ -268,7 +268,7 @@ void queryCreationShouldFeelGood() {

verify(this.result).stream();
verify(this.result).consume();
verify(this.resultSummary).notifications();
verify(this.resultSummary).gqlStatusObjects();
verify(this.resultSummary).hasPlan();
verify(this.record1).asMap();
verify(this.record2).asMap();
Expand Down Expand Up @@ -305,7 +305,7 @@ void databaseSelectionShouldBePossibleOnlyOnce() {
verify(this.session).run(eq(cypher), MockitoHamcrest.argThat(new MapAssertionMatcher(expectedParameters)));
verify(this.result).stream();
verify(this.result).consume();
verify(this.resultSummary).notifications();
verify(this.resultSummary).gqlStatusObjects();
verify(this.resultSummary).hasPlan();
verify(this.record1).asMap();
verify(this.session).close();
Expand Down Expand Up @@ -350,7 +350,7 @@ void databaseSelectionBeanShouldGetRespectedIfExisting() {
verify(this.session).run(eq(query), anyMap());
verify(this.result).stream();
verify(this.result).consume();
verify(this.resultSummary).notifications();
verify(this.resultSummary).gqlStatusObjects();
verify(this.resultSummary).hasPlan();
verify(this.record1).asMap();
verify(this.session).close();
Expand Down Expand Up @@ -379,7 +379,7 @@ void queriesWithoutResultShouldFitInAsWell() {

verify(this.session).run(eq(cypher), MockitoHamcrest.argThat(new MapAssertionMatcher(expectedParameters)));
verify(this.result).consume();
verify(this.resultSummary).notifications();
verify(this.resultSummary).gqlStatusObjects();
verify(this.resultSummary).hasPlan();
verify(this.session).close();
}
Expand Down Expand Up @@ -577,7 +577,7 @@ void reading() {
MockitoHamcrest.argThat(new MapAssertionMatcher(expectedParameters)));
verify(Neo4jClientTests.this.result).stream();
verify(Neo4jClientTests.this.result).consume();
verify(Neo4jClientTests.this.resultSummary).notifications();
verify(Neo4jClientTests.this.resultSummary).gqlStatusObjects();
verify(Neo4jClientTests.this.resultSummary).hasPlan();
verify(Neo4jClientTests.this.record1).get("name");
verify(Neo4jClientTests.this.session).close();
Expand Down Expand Up @@ -614,7 +614,7 @@ void shouldApplyNullChecksDuringReading() {
MockitoHamcrest.argThat(new MapAssertionMatcher(Collections.emptyMap())));
verify(Neo4jClientTests.this.result).stream();
verify(Neo4jClientTests.this.result).consume();
verify(Neo4jClientTests.this.resultSummary).notifications();
verify(Neo4jClientTests.this.resultSummary).gqlStatusObjects();
verify(Neo4jClientTests.this.resultSummary).hasPlan();
verify(Neo4jClientTests.this.record1).get("name");
verify(Neo4jClientTests.this.session).close();
Expand Down Expand Up @@ -646,7 +646,7 @@ void writing() {
verify(Neo4jClientTests.this.session).run(eq(cypher),
MockitoHamcrest.argThat(new MapAssertionMatcher(expectedParameters)));
verify(Neo4jClientTests.this.result).consume();
verify(Neo4jClientTests.this.resultSummary).notifications();
verify(Neo4jClientTests.this.resultSummary).gqlStatusObjects();
verify(Neo4jClientTests.this.resultSummary).hasPlan();
verify(Neo4jClientTests.this.session).close();
}
Expand Down Expand Up @@ -677,7 +677,7 @@ void automaticConversion() {
verify(Neo4jClientTests.this.result).hasNext();
verify(Neo4jClientTests.this.result).single();
verify(Neo4jClientTests.this.result).consume();
verify(Neo4jClientTests.this.resultSummary).notifications();
verify(Neo4jClientTests.this.resultSummary).gqlStatusObjects();
verify(Neo4jClientTests.this.resultSummary).hasPlan();
verify(Neo4jClientTests.this.session).close();
}
Expand Down
Loading