org.neo4j.driver.internal.util.MetadataExtractor Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of neo4j-java-driver Show documentation
Show all versions of neo4j-java-driver Show documentation
Access to the Neo4j graph database through Java
The newest version!
/*
* Copyright (c) "Neo4j"
* Neo4j Sweden AB [https://neo4j.com]
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.neo4j.driver.internal.util;
import static java.util.Collections.unmodifiableSet;
import static java.util.stream.Collectors.collectingAndThen;
import static java.util.stream.Collectors.teeing;
import static java.util.stream.Collectors.toCollection;
import static java.util.stream.Collectors.toUnmodifiableList;
import static org.neo4j.driver.internal.summary.InternalDatabaseInfo.DEFAULT_DATABASE_INFO;
import static org.neo4j.driver.internal.value.NullValue.NULL;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.OptionalInt;
import java.util.Set;
import java.util.TreeSet;
import java.util.function.Function;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;
import org.neo4j.driver.NotificationClassification;
import org.neo4j.driver.NotificationSeverity;
import org.neo4j.driver.Query;
import org.neo4j.driver.Value;
import org.neo4j.driver.Values;
import org.neo4j.driver.exceptions.ProtocolException;
import org.neo4j.driver.internal.InternalNotificationSeverity;
import org.neo4j.driver.internal.bolt.api.BoltConnection;
import org.neo4j.driver.internal.summary.InternalDatabaseInfo;
import org.neo4j.driver.internal.summary.InternalGqlStatusObject;
import org.neo4j.driver.internal.summary.InternalInputPosition;
import org.neo4j.driver.internal.summary.InternalNotification;
import org.neo4j.driver.internal.summary.InternalPlan;
import org.neo4j.driver.internal.summary.InternalProfiledPlan;
import org.neo4j.driver.internal.summary.InternalResultSummary;
import org.neo4j.driver.internal.summary.InternalServerInfo;
import org.neo4j.driver.internal.summary.InternalSummaryCounters;
import org.neo4j.driver.summary.DatabaseInfo;
import org.neo4j.driver.summary.GqlStatusObject;
import org.neo4j.driver.summary.InputPosition;
import org.neo4j.driver.summary.Notification;
import org.neo4j.driver.summary.Plan;
import org.neo4j.driver.summary.ProfiledPlan;
import org.neo4j.driver.summary.QueryType;
import org.neo4j.driver.summary.ResultSummary;
import org.neo4j.driver.summary.ServerInfo;
import org.neo4j.driver.types.MapAccessor;
import org.neo4j.driver.types.TypeSystem;
public class MetadataExtractor {
public static final int ABSENT_QUERY_ID = -1;
private static final String UNEXPECTED_TYPE_MSG_FMT = "Unexpected query type '%s', consider updating the driver";
private static final Function UNEXPECTED_TYPE_EXCEPTION_SUPPLIER =
(type) -> new ProtocolException(String.format(UNEXPECTED_TYPE_MSG_FMT, type));
private static final Comparator GQL_STATUS_OBJECT_COMPARATOR =
Comparator.comparingInt(gqlStatusObject -> {
var status = gqlStatusObject.gqlStatus();
if (status.startsWith("02")) {
return 0;
} else if (status.startsWith("01")) {
return 1;
} else if (status.startsWith("00")) {
return 2;
} else if (status.startsWith("03")) {
return 3;
} else {
return 4;
}
});
private final String resultConsumedAfterMetadataKey;
public MetadataExtractor(String resultConsumedAfterMetadataKey) {
this.resultConsumedAfterMetadataKey = resultConsumedAfterMetadataKey;
}
public ResultSummary extractSummary(
Query query,
BoltConnection connection,
long resultAvailableAfter,
Map metadata,
boolean legacyNotifications,
GqlStatusObject gqlStatusObject) {
ServerInfo serverInfo = new InternalServerInfo(
connection.serverAgent(), connection.serverAddress(), connection.protocolVersion());
var dbInfo = extractDatabaseInfo(metadata);
Set gqlStatusObjects;
List notifications;
if (legacyNotifications) {
var gqlStatusObjectsAndNotifications = extractGqlStatusObjectsFromNotifications(metadata)
.collect(teeing(
collectingAndThen(
toCollection(
() -> (Set) new TreeSet<>(GQL_STATUS_OBJECT_COMPARATOR)),
set -> {
if (gqlStatusObject != null) {
set.add(gqlStatusObject);
}
return unmodifiableSet(set);
}),
toUnmodifiableList(),
GqlStatusObjectsAndNotifications::new));
gqlStatusObjects = gqlStatusObjectsAndNotifications.gqlStatusObjects();
notifications = gqlStatusObjectsAndNotifications.notifications();
} else {
gqlStatusObjects = extractGqlStatusObjects(metadata)
.collect(collectingAndThen(
toCollection(() -> (Set) new LinkedHashSet()),
Collections::unmodifiableSet));
notifications = gqlStatusObjects.stream()
.flatMap(status -> status instanceof Notification ? Stream.of((Notification) status) : null)
.toList();
}
return new InternalResultSummary(
query,
serverInfo,
dbInfo,
extractQueryType(metadata),
extractCounters(metadata),
extractPlan(metadata),
extractProfiledPlan(metadata),
notifications,
gqlStatusObjects,
resultAvailableAfter,
extractResultConsumedAfter(metadata, resultConsumedAfterMetadataKey));
}
static DatabaseInfo extractDatabaseInfo(Map metadata) {
var dbValue = metadata.get("db");
if (dbValue == null || dbValue.isNull()) {
return DEFAULT_DATABASE_INFO;
} else {
return new InternalDatabaseInfo(dbValue.asString());
}
}
private static QueryType extractQueryType(Map metadata) {
var typeValue = metadata.get("type");
if (typeValue != null) {
return QueryType.fromCode(typeValue.asString(), UNEXPECTED_TYPE_EXCEPTION_SUPPLIER);
}
return null;
}
private static InternalSummaryCounters extractCounters(Map metadata) {
var countersValue = metadata.get("stats");
if (countersValue != null) {
return new InternalSummaryCounters(
counterValue(countersValue, "nodes-created"),
counterValue(countersValue, "nodes-deleted"),
counterValue(countersValue, "relationships-created"),
counterValue(countersValue, "relationships-deleted"),
counterValue(countersValue, "properties-set"),
counterValue(countersValue, "labels-added"),
counterValue(countersValue, "labels-removed"),
counterValue(countersValue, "indexes-added"),
counterValue(countersValue, "indexes-removed"),
counterValue(countersValue, "constraints-added"),
counterValue(countersValue, "constraints-removed"),
counterValue(countersValue, "system-updates"));
}
return null;
}
private static int counterValue(Value countersValue, String name) {
var value = countersValue.get(name);
return value.isNull() ? 0 : value.asInt();
}
private static Plan extractPlan(Map metadata) {
var planValue = metadata.get("plan");
if (planValue != null) {
return InternalPlan.EXPLAIN_PLAN_FROM_VALUE.apply(planValue);
}
return null;
}
private static ProfiledPlan extractProfiledPlan(Map metadata) {
var profiledPlanValue = metadata.get("profile");
if (profiledPlanValue != null) {
return InternalProfiledPlan.PROFILED_PLAN_FROM_VALUE.apply(profiledPlanValue);
}
return null;
}
private static Stream extractGqlStatusObjectsFromNotifications(Map metadata) {
var notificationsValue = metadata.get("notifications");
if (notificationsValue != null && TypeSystem.getDefault().LIST().isTypeOf(notificationsValue)) {
var iterable = notificationsValue.values(value -> {
var code = value.get("code").asString();
var title = value.get("title").asString();
var description = value.get("description").asString();
var rawSeverityLevel =
value.containsKey("severity") ? value.get("severity").asString() : null;
var severityLevel =
InternalNotificationSeverity.valueOf(rawSeverityLevel).orElse(null);
var rawCategory =
value.containsKey("category") ? value.get("category").asString() : null;
var category = InternalNotification.valueOf(rawCategory).orElse(null);
var posValue = value.get("position");
InputPosition position = null;
if (posValue != NULL) {
position = new InternalInputPosition(
posValue.get("offset").asInt(),
posValue.get("line").asInt(),
posValue.get("column").asInt());
}
var gqlStatusCode = "03N42";
var gqlStatusDescription = description;
if (NotificationSeverity.WARNING.equals(severityLevel)) {
gqlStatusCode = "01N42";
if (gqlStatusDescription == null || "null".equals(gqlStatusDescription)) {
gqlStatusDescription = "warn: unknown warning";
}
} else {
if (gqlStatusDescription == null || "null".equals(gqlStatusDescription)) {
gqlStatusDescription = "info: unknown notification";
}
}
var diagnosticRecord = new HashMap(3);
diagnosticRecord.put("OPERATION", Values.value(""));
diagnosticRecord.put("OPERATION_CODE", Values.value("0"));
diagnosticRecord.put("CURRENT_SCHEMA", Values.value("/"));
if (rawSeverityLevel != null) {
diagnosticRecord.put("_severity", Values.value(rawSeverityLevel));
}
if (rawCategory != null) {
diagnosticRecord.put("_classification", Values.value(rawCategory));
}
if (position != null) {
diagnosticRecord.put(
"_position",
Values.value(Map.of(
"offset",
Values.value(position.offset()),
"line",
Values.value(position.line()),
"column",
Values.value(position.column()))));
}
return new InternalNotification(
gqlStatusCode,
gqlStatusDescription,
Collections.unmodifiableMap(diagnosticRecord),
code,
title,
description,
severityLevel,
rawSeverityLevel,
(NotificationClassification) category,
rawCategory,
position);
});
return StreamSupport.stream(iterable.spliterator(), false).map(Notification.class::cast);
} else {
return Stream.empty();
}
}
private static Stream extractGqlStatusObjects(Map metadata) {
var statuses = metadata.get("statuses");
if (statuses != null && TypeSystem.getDefault().LIST().isTypeOf(statuses)) {
var iterable = statuses.values(MetadataExtractor::extractGqlStatusObject);
return StreamSupport.stream(iterable.spliterator(), false);
} else {
return Stream.empty();
}
}
@SuppressWarnings("DuplicatedCode")
private static GqlStatusObject extractGqlStatusObject(Value value) {
var status = value.get("gql_status").asString();
var description = value.get("status_description").asString();
Map diagnosticRecord;
var diagnosticRecordValue = value.get("diagnostic_record");
if (diagnosticRecordValue != null && TypeSystem.getDefault().MAP().isTypeOf(diagnosticRecordValue)) {
var containsOperation = diagnosticRecordValue.containsKey("OPERATION");
var containsOperationCode = diagnosticRecordValue.containsKey("OPERATION_CODE");
var containsCurrentSchema = diagnosticRecordValue.containsKey("CURRENT_SCHEMA");
if (containsOperation && containsOperationCode && containsCurrentSchema) {
diagnosticRecord = diagnosticRecordValue.asMap(Values::value);
} else {
diagnosticRecord = new HashMap<>(diagnosticRecordValue.asMap(Values::value));
if (!containsOperation) {
diagnosticRecord.put("OPERATION", Values.value(""));
}
if (!containsOperationCode) {
diagnosticRecord.put("OPERATION_CODE", Values.value("0"));
}
if (!containsCurrentSchema) {
diagnosticRecord.put("CURRENT_SCHEMA", Values.value("/"));
}
diagnosticRecord = Collections.unmodifiableMap(diagnosticRecord);
}
} else {
diagnosticRecord = Map.ofEntries(
Map.entry("OPERATION", Values.value("")),
Map.entry("OPERATION_CODE", Values.value("0")),
Map.entry("CURRENT_SCHEMA", Values.value("/")));
}
var neo4jCode = value.get("neo4j_code").asString(null);
if (neo4jCode == null || neo4jCode.trim().isEmpty()) {
return new InternalGqlStatusObject(status, description, diagnosticRecord);
} else {
var title = value.get("title").asString();
var notificationDescription =
value.containsKey("description") ? value.get("description").asString() : description;
var positionValue = diagnosticRecord.get("_position");
InputPosition position = null;
if (positionValue != null && TypeSystem.getDefault().MAP().isTypeOf(positionValue)) {
var offset = getAsInt(positionValue, "offset");
var line = getAsInt(positionValue, "line");
var column = getAsInt(positionValue, "column");
if (Stream.of(offset, line, column).allMatch(OptionalInt::isPresent)) {
position = new InternalInputPosition(offset.getAsInt(), line.getAsInt(), column.getAsInt());
}
}
var severityValue = diagnosticRecord.get("_severity");
String rawSeverity = null;
if (severityValue != null && TypeSystem.getDefault().STRING().isTypeOf(severityValue)) {
rawSeverity = severityValue.asString();
}
var severity = InternalNotificationSeverity.valueOf(rawSeverity).orElse(null);
var classificationValue = diagnosticRecord.get("_classification");
String rawClassification = null;
if (classificationValue != null && TypeSystem.getDefault().STRING().isTypeOf(classificationValue)) {
rawClassification = classificationValue.asString();
}
var classification = (NotificationClassification)
InternalNotification.valueOf(rawClassification).orElse(null);
return new InternalNotification(
status,
description,
diagnosticRecord,
neo4jCode,
title,
notificationDescription,
severity,
rawSeverity,
classification,
rawClassification,
position);
}
}
private static OptionalInt getAsInt(MapAccessor mapAccessor, String key) {
var value = mapAccessor.get(key);
if (value != null && TypeSystem.getDefault().INTEGER().isTypeOf(value)) {
return OptionalInt.of(value.asInt());
} else {
return OptionalInt.empty();
}
}
private static long extractResultConsumedAfter(Map metadata, String key) {
var resultConsumedAfterValue = metadata.get(key);
if (resultConsumedAfterValue != null) {
return resultConsumedAfterValue.asLong();
}
return -1;
}
private record GqlStatusObjectsAndNotifications(
Set gqlStatusObjects, List notifications) {}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy