From 6135beceda845784e2f4f8c744811dc90e589e94 Mon Sep 17 00:00:00 2001 From: Florent Biville Date: Mon, 10 Nov 2025 13:03:21 +0100 Subject: [PATCH 1/4] docs: add missing javadoc for `ImportSpecification` and friends BREAKING CHANGE: `Targets#getAll` becomes `Targets#getAllActive` --- .../org/neo4j/importer/v1/Configuration.java | 31 ++++++++-- .../importer/v1/ImportSpecification.java | 49 ++++++++++++++++ .../org/neo4j/importer/v1/actions/Action.java | 16 ++++++ .../importer/v1/actions/ActionProvider.java | 14 +++++ .../importer/v1/actions/ActionStage.java | 31 ++++++++++ .../v1/actions/plugin/CypherAction.java | 5 ++ .../importer/v1/pipeline/ImportPipeline.java | 6 +- .../org/neo4j/importer/v1/sources/Source.java | 27 +++++---- .../importer/v1/sources/SourceProvider.java | 14 +++++ .../v1/targets/CustomQueryTarget.java | 16 ++++++ .../EntityTargetExtensionProvider.java | 10 ++++ .../neo4j/importer/v1/targets/KeyMapping.java | 11 ++++ .../importer/v1/targets/NodeMatchMode.java | 12 ++++ .../importer/v1/targets/NodeReference.java | 18 ++++++ .../neo4j/importer/v1/targets/NodeSchema.java | 56 +++++++++++++++++++ .../neo4j/importer/v1/targets/NodeTarget.java | 20 +++++++ .../v1/targets/RelationshipSchema.java | 56 +++++++++++++++++++ .../v1/targets/RelationshipTarget.java | 23 ++++++++ .../neo4j/importer/v1/targets/Targets.java | 33 ++++++++++- .../neo4j/importer/v1/targets/WriteMode.java | 12 ++++ .../v1/validation/SpecificationValidator.java | 41 ++++++++------ .../neo4j/importer/v1/ConfigurationTest.java | 7 ++- .../neo4j/importer/v1/e2e/AdminImportIT.java | 7 ++- .../neo4j/importer/Neo4jAdminExampleIT.java | 6 +- 24 files changed, 475 insertions(+), 46 deletions(-) diff --git a/core/src/main/java/org/neo4j/importer/v1/Configuration.java b/core/src/main/java/org/neo4j/importer/v1/Configuration.java index 288db9d4..0be33f05 100644 --- a/core/src/main/java/org/neo4j/importer/v1/Configuration.java +++ b/core/src/main/java/org/neo4j/importer/v1/Configuration.java @@ -24,6 +24,10 @@ import java.util.Optional; import java.util.stream.Stream; +/** + * {@link Configuration} is a simple, type-safe wrapper class over arbitrary top-level {@link ImportSpecification} + * settings. + */ public class Configuration implements Serializable { private final Map settings; @@ -32,21 +36,25 @@ public Configuration(Map settings) { this.settings = settings != null ? settings : Collections.emptyMap(); } + /** + * Returns the setting by its primary name (or its alternate names), if it conforms to the provided type + * @param type expected value type of the settings + * @param name primary setting name + * @param alternativeNames alternative setting names, if any + * @return the setting value if it matches the provided parameters in an optional wrapper, or an empty optional + * @param expected type of the setting value + */ public Optional get(Class type, String name, String... alternativeNames) { if (settings.isEmpty()) { return Optional.empty(); } return Stream.concat(Stream.of(name), Arrays.stream(alternativeNames)) - .map(key -> getElement(type, key)) + .map(key -> get(type, key)) .dropWhile(Optional::isEmpty) .flatMap(Optional::stream) .findFirst(); } - private Optional getElement(Class type, String name) { - return settings.containsKey(name) ? Optional.of(type.cast(settings.get(name))) : Optional.empty(); - } - @Override public boolean equals(Object o) { if (this == o) return true; @@ -64,4 +72,17 @@ public int hashCode() { public String toString() { return "Configuration{" + "settings=" + settings + '}'; } + + private Optional get(Class type, String name) { + return settings.containsKey(name) ? Optional.ofNullable(safeCast(type, name)) : Optional.empty(); + } + + private T safeCast(Class type, String name) { + var rawValue = settings.get(name); + try { + return type.cast(rawValue); + } catch (ClassCastException e) { + return null; + } + } } diff --git a/core/src/main/java/org/neo4j/importer/v1/ImportSpecification.java b/core/src/main/java/org/neo4j/importer/v1/ImportSpecification.java index 256469e8..6c28f8a4 100644 --- a/core/src/main/java/org/neo4j/importer/v1/ImportSpecification.java +++ b/core/src/main/java/org/neo4j/importer/v1/ImportSpecification.java @@ -27,6 +27,35 @@ import org.neo4j.importer.v1.sources.Source; import org.neo4j.importer.v1.targets.Targets; +/** + * {@link ImportSpecification} is the direct representation of the import-spec configuration YAML/JSON files.
+ * {@link ImportSpecification} is made of 3 mandatory attributes: + *
    + *
  • version
  • + *
  • a list of {@link Source}
  • + *
  • targets, which is a mix of: + *
      + *
    • {@link org.neo4j.importer.v1.targets.NodeTarget}
    • + *
    • {@link org.neo4j.importer.v1.targets.RelationshipTarget}
    • + *
    • {@link org.neo4j.importer.v1.targets.CustomQueryTarget}
    • + *
    + *
+ * {@link ImportSpecification} can also include: + *
    + *
  • top-level settings, wrapped into {@link Configuration}
  • + *
  • one-off {@link Action} scripts
  • + *

+ * Note: It is strongly discouraged to instantiate this class directly. + * Please call one of the deserialization APIs of {@link ImportSpecificationDeserializer} instead. + * The deserializer automatically loads all built-in and external + * {@link org.neo4j.importer.v1.validation.SpecificationValidator} implementation and runs them against this.
+ * The direct use of {@link ImportSpecification} is most suited when targeting import tools that fully manage import + * task ordering such as neo4j-admin (neo4j-admin handles the order between schema initialization, node import and + * relationship import itself).
+ * In other cases, {@link org.neo4j.importer.v1.pipeline.ImportPipeline} is a better choice.
+ * You can construct an {@link org.neo4j.importer.v1.pipeline.ImportPipeline} instance from an instance of + * {@link ImportSpecification} with {@link org.neo4j.importer.v1.pipeline.ImportPipeline#of(ImportSpecification)}. + */ public class ImportSpecification implements Serializable { private final String version; @@ -54,22 +83,42 @@ public ImportSpecification( this.actions = actions; } + /** + * Returns the {@link ImportSpecification} version + * @return the version + */ public String getVersion() { return version; } + /** + * Returns the {@link ImportSpecification} top-level settings + * @return the top-level settings + */ public Configuration getConfiguration() { return configuration; } + /** + * Returns the {@link ImportSpecification} data sources + * @return the data sources + */ public List getSources() { return sources; } + /** + * Returns the {@link ImportSpecification} node, relationship and custom query targets, wrapped into {@link Targets} + * @return the targets + */ public Targets getTargets() { return targets; } + /** + * Returns the {@link ImportSpecification} actions, active or not + * @return the actions + */ public List getActions() { return actions != null ? actions : Collections.emptyList(); } diff --git a/core/src/main/java/org/neo4j/importer/v1/actions/Action.java b/core/src/main/java/org/neo4j/importer/v1/actions/Action.java index ad67f7e2..e14352dd 100644 --- a/core/src/main/java/org/neo4j/importer/v1/actions/Action.java +++ b/core/src/main/java/org/neo4j/importer/v1/actions/Action.java @@ -18,10 +18,26 @@ import java.io.Serializable; +/** + * {@link Action} define one-off execution scripts.
+ * The {@link Action} name must be unique within an instance of {@link org.neo4j.importer.v1.ImportSpecification}.
+ * The {@link Action} stage (see {@link Action#getStage()} determines when an action must be executed.
+ * Only {@link org.neo4j.importer.v1.actions.plugin.CypherAction} is defined by default.
+ * Other {@link ActionProvider} implementation may be registered through Java's standard + * Service Provider Interface mechanism. + */ public interface Action extends Serializable { boolean DEFAULT_ACTIVE = true; + /** + * Type of the action (example: "jdbc", "http", ...)
+ * The type name must be unique in a case-insensitive (per {@link java.util.Locale#ROOT} casing rules).
+ * Having multiple actions loaded with the same type is invalid and will lead to an exception being raised, aborting + * the import early.
+ * Note: it is recommended that the type returned here be the same as the one this source's {@link ActionProvider} + * supports, or a subset thereof. + */ String getType(); /** diff --git a/core/src/main/java/org/neo4j/importer/v1/actions/ActionProvider.java b/core/src/main/java/org/neo4j/importer/v1/actions/ActionProvider.java index 8038666e..1cb2408c 100644 --- a/core/src/main/java/org/neo4j/importer/v1/actions/ActionProvider.java +++ b/core/src/main/java/org/neo4j/importer/v1/actions/ActionProvider.java @@ -19,10 +19,24 @@ import com.fasterxml.jackson.databind.node.ObjectNode; import java.util.function.Function; +/** + * {@link ActionProvider} is a Service Provider Interface, allowing users to define their own action. + * @param the concrete type of the {@link Action} subclass + */ public interface ActionProvider extends Function { + /** + * User-facing type of the concrete supported action, it must match the result of {@link Action#getType()} + * @return the action type + */ String supportedType(); + /** + * Deserialization endpoint.
+ * Note: implement deserialization leniently, validate strictly (define + * {@link org.neo4j.importer.v1.validation.SpecificationValidator} for your own sources) + * @return the deserialized action + */ @Override default T apply(ObjectNode node) { return provide(node); diff --git a/core/src/main/java/org/neo4j/importer/v1/actions/ActionStage.java b/core/src/main/java/org/neo4j/importer/v1/actions/ActionStage.java index c41a8475..4577df27 100644 --- a/core/src/main/java/org/neo4j/importer/v1/actions/ActionStage.java +++ b/core/src/main/java/org/neo4j/importer/v1/actions/ActionStage.java @@ -16,14 +16,45 @@ */ package org.neo4j.importer.v1.actions; +/** + * {@link ActionStage} defines when the enclosing {@link Action} must run.
+ * {@link org.neo4j.importer.v1.pipeline.ImportPipeline} translate this into concrete dependencies. + */ public enum ActionStage { + /** + * The enclosing {@link Action} must run before anything else (actively-used sources, active targets) + */ START, + /** + * The enclosing {@link Action} must run after all actively-used {@link org.neo4j.importer.v1.sources.Source}s + */ POST_SOURCES, + /** + * The enclosing {@link Action} must run before all active {@link org.neo4j.importer.v1.targets.NodeTarget}s + */ PRE_NODES, + /** + * The enclosing {@link Action} must run after all active {@link org.neo4j.importer.v1.targets.NodeTarget}s + */ POST_NODES, + /** + * The enclosing {@link Action} must run before all active {@link org.neo4j.importer.v1.targets.RelationshipTarget}s + */ PRE_RELATIONSHIPS, + /** + * The enclosing {@link Action} must run after all active {@link org.neo4j.importer.v1.targets.RelationshipTarget}s + */ POST_RELATIONSHIPS, + /** + * The enclosing {@link Action} must run before all active {@link org.neo4j.importer.v1.targets.CustomQueryTarget}s + */ PRE_QUERIES, + /** + * The enclosing {@link Action} must run after all active {@link org.neo4j.importer.v1.targets.CustomQueryTarget}s + */ POST_QUERIES, + /** + * The enclosing {@link Action} must run after everything else (actively-used sources, active targets) + */ END } diff --git a/core/src/main/java/org/neo4j/importer/v1/actions/plugin/CypherAction.java b/core/src/main/java/org/neo4j/importer/v1/actions/plugin/CypherAction.java index 70efda5a..3f3bb837 100644 --- a/core/src/main/java/org/neo4j/importer/v1/actions/plugin/CypherAction.java +++ b/core/src/main/java/org/neo4j/importer/v1/actions/plugin/CypherAction.java @@ -20,6 +20,11 @@ import org.neo4j.importer.v1.actions.Action; import org.neo4j.importer.v1.actions.ActionStage; +/** + * {@link CypherAction} defines a one-off Cypher script to execute.
+ * It defines a query (see {@link CypherAction#getQuery()} and an execution mode {@link CypherExecutionMode} (default: + * {@link CypherExecutionMode#TRANSACTION}). + */ public class CypherAction implements Action { static final CypherExecutionMode DEFAULT_CYPHER_EXECUTION_MODE = CypherExecutionMode.TRANSACTION; diff --git a/core/src/main/java/org/neo4j/importer/v1/pipeline/ImportPipeline.java b/core/src/main/java/org/neo4j/importer/v1/pipeline/ImportPipeline.java index 8f3c9707..6e5c78b8 100644 --- a/core/src/main/java/org/neo4j/importer/v1/pipeline/ImportPipeline.java +++ b/core/src/main/java/org/neo4j/importer/v1/pipeline/ImportPipeline.java @@ -102,7 +102,7 @@ private static Map> buildDependencyNameGraph( ImportSpecification importSpecification) { Map> dependencyGraph = new HashMap<>(); Targets targets = importSpecification.getTargets(); - var activeTargets = targets.getAll().stream().filter(Target::isActive).collect(Collectors.toList()); + var activeTargets = targets.getAllActive(); var activeSources = activeTargets.stream().map(Target::getSource).distinct().collect(Collectors.toList()); var sources = new LinkedHashSet(activeSources.size()); @@ -228,12 +228,16 @@ private static Map> resolveNames( var indexedSources = importSpecification.getSources().stream() .collect(Collectors.toMap(Source::getName, Function.identity())); var indexedNodeTargets = importSpecification.getTargets().getNodes().stream() + .filter(Target::isActive) .collect(Collectors.toMap(Target::getName, Function.identity())); var indexedRelationshipTargets = importSpecification.getTargets().getRelationships().stream() + .filter(Target::isActive) .collect(Collectors.toMap(Target::getName, Function.identity())); var indexedQueryTargets = importSpecification.getTargets().getCustomQueries().stream() + .filter(Target::isActive) .collect(Collectors.toMap(Target::getName, Function.identity())); var indexedActions = importSpecification.getActions().stream() + .filter(Action::isActive) .collect(Collectors.toMap(Action::getName, Function.identity())); var processedNodeSteps = new HashMap(); var processedSteps = new HashMap(); diff --git a/core/src/main/java/org/neo4j/importer/v1/sources/Source.java b/core/src/main/java/org/neo4j/importer/v1/sources/Source.java index 9a7653f1..0fa8308b 100644 --- a/core/src/main/java/org/neo4j/importer/v1/sources/Source.java +++ b/core/src/main/java/org/neo4j/importer/v1/sources/Source.java @@ -19,27 +19,30 @@ import java.io.Serializable; /** - * A {@link Source} provides a collection of key-value pairs. - * Each {@link org.neo4j.importer.v1.targets.Target} is bound to a single {@link Source}, using the source's name. - * The source's key-value pairs are mapped by the target's {@link org.neo4j.importer.v1.targets.PropertyMapping} in the case - * of {@link org.neo4j.importer.v1.targets.NodeTarget} and {@link org.neo4j.importer.v1.targets.RelationshipTarget}. - * They are otherwise arbitrarily mapped by the Cypher query defined by {@link org.neo4j.importer.v1.targets.CustomQueryTarget}. - * A source name must be unique within an instance of {@link org.neo4j.importer.v1.ImportSpecification}. - * A custom source can only be used if a corresponding {@link SourceProvider} is registered, through Java's standard + * A {@link Source} provides rows of key-value pairs, also referred to as fields.
+ * Each {@link org.neo4j.importer.v1.targets.Target} is bound to a single {@link Source}, using the source's name.
+ * {@link org.neo4j.importer.v1.targets.PropertyMapping}s govern how source fields are mapped to node or relationship + * properties.
+ * They are otherwise arbitrarily mapped by the Cypher query defined by + * {@link org.neo4j.importer.v1.targets.CustomQueryTarget}.
+ * A source name must be unique within an instance of {@link org.neo4j.importer.v1.ImportSpecification}.
+ * At least one {@link SourceProvider} implementation must be registered through Java's standard * Service Provider Interface mechanism. */ public interface Source extends Serializable { /** - * Type of the source (example: "jdbc", "text", "parquet", ...) - * The type name must be unique in a case-insensitive (per {@link java.util.Locale#ROOT} casing rules). - * Having multiple sources loaded with the same type is invalid and will lead to an exception being raised. - * Note: it is recommended that the type returned here be the same as the one this source's {@link SourceProvider} supports. + * Type of the source (example: "jdbc", "text", "parquet", ...)
+ * The type name must be unique in a case-insensitive (per {@link java.util.Locale#ROOT} casing rules).
+ * Having multiple sources loaded with the same type is invalid and will lead to an exception being raised, aborting + * the import early.
+ * Note: it is recommended that the type returned here be the same as the one this source's {@link SourceProvider} + * supports. */ String getType(); /** - * Name of a particular source instance + * Name of a particular source instance
* The name is user-provided and must be unique within the whole {@link org.neo4j.importer.v1.ImportSpecification}. * This name is used as a reference for {@link org.neo4j.importer.v1.targets.Target}s to declare the source of the * data they map from. diff --git a/core/src/main/java/org/neo4j/importer/v1/sources/SourceProvider.java b/core/src/main/java/org/neo4j/importer/v1/sources/SourceProvider.java index 133f52dc..7f1f81f3 100644 --- a/core/src/main/java/org/neo4j/importer/v1/sources/SourceProvider.java +++ b/core/src/main/java/org/neo4j/importer/v1/sources/SourceProvider.java @@ -19,10 +19,24 @@ import com.fasterxml.jackson.databind.node.ObjectNode; import java.util.function.Function; +/** + * {@link SourceProvider} is a Service Provider Interface, allowing users to define their own source. + * @param the concrete type of the {@link Source} subclass + */ public interface SourceProvider extends Function { + /** + * User-facing type of the concrete supported source, it must match the result of {@link Source#getType()} + * @return the source type + */ String supportedType(); + /** + * Deserialization endpoint.
+ * Note: implement deserialization leniently, validate strictly (define + * {@link org.neo4j.importer.v1.validation.SpecificationValidator} for your own sources) + * @return the deserialized source + */ @Override default T apply(ObjectNode node) { return provide(node); diff --git a/core/src/main/java/org/neo4j/importer/v1/targets/CustomQueryTarget.java b/core/src/main/java/org/neo4j/importer/v1/targets/CustomQueryTarget.java index bd4e2796..ebacd56d 100644 --- a/core/src/main/java/org/neo4j/importer/v1/targets/CustomQueryTarget.java +++ b/core/src/main/java/org/neo4j/importer/v1/targets/CustomQueryTarget.java @@ -21,6 +21,22 @@ import java.util.List; import java.util.Objects; +/** + * {@link CustomQueryTarget} allows users to arbitrary map data from a particular source to the target database, with + * the means of a Cypher query.
+ * In particular, {@link CustomQueryTarget} defines these mandatory attributes: + *
    + *
  • the name of the source is maps data from ({@link CustomQueryTarget#getSource()})
  • + *
  • the Cypher query that performs the import
  • + *
+ * {@link CustomQueryTarget} can optionally specify:
+ *
    + *
  • whether the target is active or not ({@link CustomQueryTarget#isActive()}): backends must skip inactive targets
  • + *
  • dependencies on other targets (by their names, see {@link CustomQueryTarget#getDependencies()})
  • + *
+ * Note: depending on the backend, the specified query may be re-run several times (backends may batch source data, + * hence running the query at least once per batch, or more if the backend retries). + */ public class CustomQueryTarget extends Target { private final String query; diff --git a/core/src/main/java/org/neo4j/importer/v1/targets/EntityTargetExtensionProvider.java b/core/src/main/java/org/neo4j/importer/v1/targets/EntityTargetExtensionProvider.java index 924c698b..a06cc6b7 100644 --- a/core/src/main/java/org/neo4j/importer/v1/targets/EntityTargetExtensionProvider.java +++ b/core/src/main/java/org/neo4j/importer/v1/targets/EntityTargetExtensionProvider.java @@ -19,4 +19,14 @@ import com.fasterxml.jackson.databind.node.ObjectNode; import java.util.function.Function; +/** + * {@link EntityTargetExtensionProvider} is a Service Provider Interface, allowing users to define their own entity + * (node, relationship) target extension data.
+ * The deserialized extensions can then be retrieved with: + *
    + *
  • {@link NodeTarget#getExtensions()} or {@link RelationshipTarget#getExtensions()}
  • + *
  • {@link NodeTarget#getExtension(Class)} or {@link RelationshipTarget#getExtension(Class)}
  • + *
+ * @param the concrete type of the {@link EntityTargetExtension} subclass + */ public interface EntityTargetExtensionProvider extends Function {} diff --git a/core/src/main/java/org/neo4j/importer/v1/targets/KeyMapping.java b/core/src/main/java/org/neo4j/importer/v1/targets/KeyMapping.java index 153fc452..5e60696e 100644 --- a/core/src/main/java/org/neo4j/importer/v1/targets/KeyMapping.java +++ b/core/src/main/java/org/neo4j/importer/v1/targets/KeyMapping.java @@ -20,6 +20,9 @@ import com.fasterxml.jackson.annotation.JsonProperty; import java.util.Objects; +/** + * {@link KeyMapping} defines the node lookup override for the given start/end node of a {@link RelationshipTarget}. + */ public class KeyMapping { private final String sourceField; @@ -33,10 +36,18 @@ public KeyMapping( this.nodeProperty = nodeProperty; } + /** + * Source field name to use when mapping data from the enclosing {@link RelationshipTarget} source + * @return the source field + */ public String getSourceField() { return sourceField; } + /** + * Key property name to use when looking the start/end node named by the enclosing {@link NodeReference} + * @return the source field + */ public String getNodeProperty() { return nodeProperty; } diff --git a/core/src/main/java/org/neo4j/importer/v1/targets/NodeMatchMode.java b/core/src/main/java/org/neo4j/importer/v1/targets/NodeMatchMode.java index 6e69b00b..643b0b38 100644 --- a/core/src/main/java/org/neo4j/importer/v1/targets/NodeMatchMode.java +++ b/core/src/main/java/org/neo4j/importer/v1/targets/NodeMatchMode.java @@ -16,7 +16,19 @@ */ package org.neo4j.importer.v1.targets; +/** + * {@link NodeMatchMode} controls how relationship start/end nodes are looked up. + */ public enum NodeMatchMode { + /** + * This tells the backend to match the start/end node, or skip the relationship if the corresponding node is not + * found in the graph + * @see Cypher's MATCH clause + */ MATCH, + /** + * This tells the backend to find or create the start/end node + * @see Cypher's MERGE clause + */ MERGE } diff --git a/core/src/main/java/org/neo4j/importer/v1/targets/NodeReference.java b/core/src/main/java/org/neo4j/importer/v1/targets/NodeReference.java index a7b83ea9..f206b447 100644 --- a/core/src/main/java/org/neo4j/importer/v1/targets/NodeReference.java +++ b/core/src/main/java/org/neo4j/importer/v1/targets/NodeReference.java @@ -22,6 +22,16 @@ import java.util.List; import java.util.Objects; +/** + * {@link NodeReference} defines a named reference to a start/end {@link NodeTarget} of a {@link RelationshipTarget}.
+ * The corresponding {@link NodeTarget} is found by the provided name.
+ * The node lookup pattern is determined either: + *
    + *
  1. by looking original field names of the {@link NodeTarget} key properties. + *
  2. + *
  3. by key mapping overrides: {@link KeyMapping} controls the field and property names to use for the lookup
  4. + *
+ */ public class NodeReference implements Serializable { private final String name; private final List keyMappings; @@ -38,10 +48,18 @@ public NodeReference( this.keyMappings = keyMappings; } + /** + * Returns the name of the {@link NodeTarget} + * @return node target name + */ public String getName() { return name; } + /** + * Returns the node key mapping overrides + * @return the node key mapping overrides, this is never null + */ public List getKeyMappings() { return keyMappings != null ? keyMappings : List.of(); } diff --git a/core/src/main/java/org/neo4j/importer/v1/targets/NodeSchema.java b/core/src/main/java/org/neo4j/importer/v1/targets/NodeSchema.java index 947e0450..a8fce967 100644 --- a/core/src/main/java/org/neo4j/importer/v1/targets/NodeSchema.java +++ b/core/src/main/java/org/neo4j/importer/v1/targets/NodeSchema.java @@ -22,6 +22,22 @@ import java.util.List; import java.util.Objects; +/** + * {@link NodeSchema} defines the indices and constraints that must be created for the enclosing {@link NodeTarget}.
+ * All the indices and constraints below are optional: + *
    + *
  • property type constraints (this only works where the corresponding {@link PropertyMapping} has defined a + * type, see {@link NodeSchema#getTypeConstraints()})
  • + *
  • node key constraints (see {@link NodeSchema#getKeyConstraints()} ()})
  • + *
  • node unique constraints (see {@link NodeSchema#getUniqueConstraints()} ()})
  • + *
  • property existence constraints (see {@link NodeSchema#getExistenceConstraints()})
  • + *
  • range indexes (see {@link NodeSchema#getRangeIndexes()})
  • + *
  • text indexes (see {@link NodeSchema#getTextIndexes()})
  • + *
  • point indexes (see {@link NodeSchema#getPointIndexes()})
  • + *
  • full-text indexes (see {@link NodeSchema#getFullTextIndexes()})
  • + *
  • vector indexes (see {@link NodeSchema#getVectorIndexes()})
  • + *
+ */ public class NodeSchema implements Schema, Serializable { static final NodeSchema EMPTY = new NodeSchema( @@ -60,6 +76,10 @@ public NodeSchema( this.vectorIndexes = vectorIndexes; } + /** + * Whether this {@link NodeSchema} define any constraints and indices + * @return true if no schema element is defined, false otherwise + */ public boolean isEmpty() { return getTypeConstraints().isEmpty() && getKeyConstraints().isEmpty() @@ -72,38 +92,74 @@ && getFullTextIndexes().isEmpty() && getVectorIndexes().isEmpty(); } + /** + * Returns the defined property type constraints for this {@link NodeTarget} + * @return property type constraints, this is never null + */ public List getTypeConstraints() { return typeConstraints != null ? typeConstraints : List.of(); } + /** + * Returns the defined key constraints for this {@link NodeTarget} + * @return key constraints, this is never null + */ public List getKeyConstraints() { return keyConstraints != null ? keyConstraints : List.of(); } + /** + * Returns the defined unique constraints for this {@link NodeTarget} + * @return unique constraints, this is never null + */ public List getUniqueConstraints() { return uniqueConstraints != null ? uniqueConstraints : List.of(); } + /** + * Returns the defined property existence constraints for this {@link NodeTarget} + * @return property existence constraints, this is never null + */ public List getExistenceConstraints() { return existenceConstraints != null ? existenceConstraints : List.of(); } + /** + * Returns the defined range indexes for this {@link NodeTarget} + * @return range indexes, this is never null + */ public List getRangeIndexes() { return rangeIndexes != null ? rangeIndexes : List.of(); } + /** + * Returns the defined text indexes for this {@link NodeTarget} + * @return text indexes, this is never null + */ public List getTextIndexes() { return textIndexes != null ? textIndexes : List.of(); } + /** + * Returns the defined point indexes for this {@link NodeTarget} + * @return point indexes, this is never null + */ public List getPointIndexes() { return pointIndexes != null ? pointIndexes : List.of(); } + /** + * Returns the defined full-text indexes for this {@link NodeTarget} + * @return full-text indexes, this is never null + */ public List getFullTextIndexes() { return fullTextIndexes != null ? fullTextIndexes : List.of(); } + /** + * Returns the defined vector indexes for this {@link NodeTarget} + * @return vector indexes, this is never null + */ public List getVectorIndexes() { return vectorIndexes != null ? vectorIndexes : List.of(); } diff --git a/core/src/main/java/org/neo4j/importer/v1/targets/NodeTarget.java b/core/src/main/java/org/neo4j/importer/v1/targets/NodeTarget.java index b6ce806f..53463798 100644 --- a/core/src/main/java/org/neo4j/importer/v1/targets/NodeTarget.java +++ b/core/src/main/java/org/neo4j/importer/v1/targets/NodeTarget.java @@ -23,6 +23,26 @@ import java.util.List; import java.util.Objects; +/** + * {@link NodeTarget} defines one kind of node to import.
+ * In particular, {@link NodeTarget} defines these mandatory attributes: + *
    + *
  • the resulting node labels ({@link NodeTarget#getLabels()})
  • + *
  • the name of the source is maps data from ({@link NodeTarget#getSource()})
  • + *
  • how source fields are mapped to node properties via {@link NodeTarget#getProperties()}
  • + *
+ * {@link NodeTarget} can optionally specify:
+ *
    + *
  • its {@link WriteMode} for backends that support it
  • + *
  • whether the target is active or not ({@link NodeTarget#isActive()}): backends must skip inactive targets
  • + *
  • dependencies on other targets (by their names, see {@link NodeTarget#getDependencies()})
  • + *
  • indices and/or constraints (via {@link NodeSchema})
  • + *
  • extensions
  • + *
+ * Extensions must be registered with {@link EntityTargetExtensionProvider} through Java's standard Service Provider + * Interface mechanism. + * @see Graph database concepts + */ public class NodeTarget extends EntityTarget { private final List labels; diff --git a/core/src/main/java/org/neo4j/importer/v1/targets/RelationshipSchema.java b/core/src/main/java/org/neo4j/importer/v1/targets/RelationshipSchema.java index b84e21aa..cf8cf50c 100644 --- a/core/src/main/java/org/neo4j/importer/v1/targets/RelationshipSchema.java +++ b/core/src/main/java/org/neo4j/importer/v1/targets/RelationshipSchema.java @@ -22,6 +22,22 @@ import java.util.List; import java.util.Objects; +/** + * {@link RelationshipSchema} defines the indices and constraints that must be created for the enclosing {@link RelationshipTarget}.
+ * All the indices and constraints below are optional: + *
    + *
  • property type constraints (this only works where the corresponding {@link PropertyMapping} has defined a + * type, see {@link RelationshipSchema#getTypeConstraints()})
  • + *
  • relationship key constraints (see {@link RelationshipSchema#getKeyConstraints()} ()})
  • + *
  • relationship unique constraints (see {@link RelationshipSchema#getUniqueConstraints()} ()})
  • + *
  • property existence constraints (see {@link RelationshipSchema#getExistenceConstraints()})
  • + *
  • range indexes (see {@link RelationshipSchema#getRangeIndexes()})
  • + *
  • text indexes (see {@link RelationshipSchema#getTextIndexes()})
  • + *
  • point indexes (see {@link RelationshipSchema#getPointIndexes()})
  • + *
  • full-text indexes (see {@link RelationshipSchema#getFullTextIndexes()})
  • + *
  • vector indexes (see {@link RelationshipSchema#getVectorIndexes()})
  • + *
+ */ public class RelationshipSchema implements Schema, Serializable { static final RelationshipSchema EMPTY = new RelationshipSchema( @@ -61,6 +77,10 @@ public RelationshipSchema( this.vectorIndexes = vectorIndexes; } + /** + * Whether this {@link RelationshipSchema} define any constraints and indices + * @return true if no schema element is defined, false otherwise + */ public boolean isEmpty() { return getTypeConstraints().isEmpty() && getKeyConstraints().isEmpty() @@ -73,38 +93,74 @@ && getFullTextIndexes().isEmpty() && getVectorIndexes().isEmpty(); } + /** + * Returns the defined property type constraints for this {@link RelationshipTarget} + * @return property type constraints, this is never null + */ public List getTypeConstraints() { return typeConstraints != null ? typeConstraints : List.of(); } + /** + * Returns the defined key constraints for this {@link RelationshipTarget} + * @return key constraints, this is never null + */ public List getKeyConstraints() { return keyConstraints != null ? keyConstraints : List.of(); } + /** + * Returns the defined unique constraints for this {@link RelationshipTarget} + * @return unique constraints, this is never null + */ public List getUniqueConstraints() { return uniqueConstraints != null ? uniqueConstraints : List.of(); } + /** + * Returns the defined property existence constraints for this {@link RelationshipTarget} + * @return property existence constraints, this is never null + */ public List getExistenceConstraints() { return existenceConstraints != null ? existenceConstraints : List.of(); } + /** + * Returns the defined range indexes for this {@link RelationshipTarget} + * @return range indexes, this is never null + */ public List getRangeIndexes() { return rangeIndexes != null ? rangeIndexes : List.of(); } + /** + * Returns the defined text indexes for this {@link RelationshipTarget} + * @return text indexes, this is never null + */ public List getTextIndexes() { return textIndexes != null ? textIndexes : List.of(); } + /** + * Returns the defined point indexes for this {@link RelationshipTarget} + * @return point indexes, this is never null + */ public List getPointIndexes() { return pointIndexes != null ? pointIndexes : List.of(); } + /** + * Returns the defined full-text indexes for this {@link RelationshipTarget} + * @return full-text indexes, this is never null + */ public List getFullTextIndexes() { return fullTextIndexes != null ? fullTextIndexes : List.of(); } + /** + * Returns the defined vector indexes for this {@link RelationshipTarget} + * @return vector indexes, this is never null + */ public List getVectorIndexes() { return vectorIndexes != null ? vectorIndexes : List.of(); } diff --git a/core/src/main/java/org/neo4j/importer/v1/targets/RelationshipTarget.java b/core/src/main/java/org/neo4j/importer/v1/targets/RelationshipTarget.java index cfa8f0bf..151659e4 100644 --- a/core/src/main/java/org/neo4j/importer/v1/targets/RelationshipTarget.java +++ b/core/src/main/java/org/neo4j/importer/v1/targets/RelationshipTarget.java @@ -23,6 +23,29 @@ import java.util.List; import java.util.Objects; +/** + * {@link RelationshipTarget} defines one kind of relationship to import.
+ * In particular, {@link RelationshipTarget} defines these mandatory attributes: + *
    + *
  • the resulting relationship type ({@link RelationshipTarget#getType()})
  • + *
  • the name of the source is maps data from ({@link RelationshipTarget#getSource()})
  • + *
  • how source fields are mapped to node properties via {@link RelationshipTarget#getProperties()}
  • + *
  • a {@link NodeReference} to its start node
  • + *
  • a {@link NodeReference} to its end node
  • + *
+ * {@link RelationshipTarget} can optionally specify:
+ *
    + *
  • its {@link WriteMode} for backends that support it
  • + *
  • its {@link NodeMatchMode} for backends that support it
  • + *
  • whether the target is active or not ({@link RelationshipTarget#isActive()}): backends must skip inactive targets
  • + *
  • dependencies on other targets (by their names, see {@link RelationshipTarget#getDependencies()})
  • + *
  • indices and/or constraints (via {@link RelationshipSchema})
  • + *
  • extensions
  • + *
+ * Extensions must be registered with {@link EntityTargetExtensionProvider} through Java's standard Service Provider + * Interface mechanism. + * @see Graph database concepts + */ public class RelationshipTarget extends EntityTarget { private final String type; private final NodeMatchMode nodeMatchMode; diff --git a/core/src/main/java/org/neo4j/importer/v1/targets/Targets.java b/core/src/main/java/org/neo4j/importer/v1/targets/Targets.java index 312afaa6..ffa1ebf6 100644 --- a/core/src/main/java/org/neo4j/importer/v1/targets/Targets.java +++ b/core/src/main/java/org/neo4j/importer/v1/targets/Targets.java @@ -23,7 +23,16 @@ import java.util.Collections; import java.util.List; import java.util.Objects; +import java.util.stream.Collectors; +/** + * {@link Targets} is a simple wrapper class for lists of: + *
    + *
  • {@link org.neo4j.importer.v1.targets.NodeTarget}
  • + *
  • {@link org.neo4j.importer.v1.targets.RelationshipTarget}
  • + *
  • {@link org.neo4j.importer.v1.targets.CustomQueryTarget}
  • + *
+ */ public class Targets implements Serializable { private final List nodes; @@ -41,7 +50,12 @@ public Targets( this.customQueries = customQueries; } - public List getAll() { + /** + * Returns all the active targets defined by the enclosing {@link org.neo4j.importer.v1.ImportSpecification} + * @return all active targets, this is never null + * @see Target#isActive() + */ + public List getAllActive() { List nodeTargets = getNodes(); List relationshipTargets = getRelationships(); List customQueryTargets = getCustomQueries(); @@ -50,17 +64,32 @@ public List getAll() { result.addAll(nodeTargets); result.addAll(relationshipTargets); result.addAll(customQueryTargets); - return result; + return result.stream().filter(Target::isActive).collect(Collectors.toList()); } + /** + * Returns all node targets, including inactive ones. + * @return all node targets, this is never null + * @see NodeTarget + */ public List getNodes() { return nodes == null ? Collections.emptyList() : nodes; } + /** + * Returns all relationship targets, including inactive ones. + * @return all relationship targets, this is never null + * @see RelationshipTarget + */ public List getRelationships() { return relationships == null ? Collections.emptyList() : relationships; } + /** + * Returns all custom query targets, including inactive ones. + * @return all custom query targets, this is never null + * @see CustomQueryTarget + */ public List getCustomQueries() { return customQueries == null ? Collections.emptyList() : customQueries; } diff --git a/core/src/main/java/org/neo4j/importer/v1/targets/WriteMode.java b/core/src/main/java/org/neo4j/importer/v1/targets/WriteMode.java index 6b839374..cba9d7b6 100644 --- a/core/src/main/java/org/neo4j/importer/v1/targets/WriteMode.java +++ b/core/src/main/java/org/neo4j/importer/v1/targets/WriteMode.java @@ -16,7 +16,19 @@ */ package org.neo4j.importer.v1.targets; +/** + * {@link WriteMode} controls how an entity (node, relationship) is written to the graph. + */ public enum WriteMode { + /** + * This tells the backend to insert the entity, regardless of similar entities already persisted in the graph + * @see Cypher's CREATE clause + */ CREATE, + /** + * This tells the backend to insert the entity, if no similar entity has been found in the graph (this can still + * lead to duplicates unless a uniqueness constraint has been set for the pattern being merged) + * @see Cypher's MERGE clause + */ MERGE } diff --git a/core/src/main/java/org/neo4j/importer/v1/validation/SpecificationValidator.java b/core/src/main/java/org/neo4j/importer/v1/validation/SpecificationValidator.java index cf0eecd6..12c31785 100644 --- a/core/src/main/java/org/neo4j/importer/v1/validation/SpecificationValidator.java +++ b/core/src/main/java/org/neo4j/importer/v1/validation/SpecificationValidator.java @@ -27,24 +27,29 @@ import org.neo4j.importer.v1.validation.SpecificationValidationResult.Builder; /** - * This is the SPI for custom validators. - * Custom validators have the ability to validate elements of an {@link org.neo4j.importer.v1.ImportSpecification}. - * The import specification at this stage is guaranteed to comply to the official import specification JSON schema. - * Every custom validator is instantiated only once (per {@link org.neo4j.importer.v1.ImportSpecificationDeserializer#deserialize(Reader)} call. - * The validation order is as follows:
- * 1. visitConfiguration
- * 2. visitSource (as many times as there are sources)
- * 3. visitNodeTarget (as many times as there are node targets)
- * 4. visitRelationshipTarget (as many times as there are relationship targets)
- * 5. visitCustomQueryTarget (as many times as there are custom query targets)
- * 6. visitAction (as many times as there are actions)
- * Then {@link SpecificationValidator#report(Builder)} is called with a {@link SpecificationValidationResult.Builder}, where - * errors are reported via {@link SpecificationValidationResult.Builder#addError(String, String, String)} and warnings - * via {@link SpecificationValidationResult.Builder#addWarning(String, String, String)}. - * Implementations are not expected to be thread-safe. - * Implementations must not make any assumptions about the invocation order of other implementations. - * If an implementation depends on the successful validation of another one, please include the latter to {@link SpecificationValidator#requires()}. - * Mutating the provided arguments via any of the visitXxx or accept calls is considered undefined behavior. + * This is the Service Provider Interface for custom validators.
+ * Custom validators have the ability to validate elements of an {@link org.neo4j.importer.v1.ImportSpecification}.
+ * The import specification at this stage is guaranteed to comply to the official import specification JSON schema.
+ * Every custom validator is instantiated only once + * (per {@link org.neo4j.importer.v1.ImportSpecificationDeserializer#deserialize(Reader)} call.
+ * The validation order is as follows: + *
    + *
  1. visitConfiguration
  2. + *
  3. visitSource (as many times as there are sources)
  4. + *
  5. visitNodeTarget (as many times as there are node targets)
  6. + *
  7. visitRelationshipTarget (as many times as there are relationship targets)
  8. + *
  9. visitCustomQueryTarget (as many times as there are custom query targets)
  10. + *
  11. visitAction (as many times as there are actions)
  12. + *
+ * Then {@link SpecificationValidator#report(Builder)} is called with a {@link SpecificationValidationResult.Builder}, + * where errors are reported via {@link SpecificationValidationResult.Builder#addError(String, String, String)} and + * warnings via {@link SpecificationValidationResult.Builder#addWarning(String, String, String)}.
+ * Implementations are not expected to be thread-safe.
+ * Implementations must not make any assumptions about the invocation order of other + * implementations.
+ * If an implementation depends on the successful validation of another one, please include the latter to + * {@link SpecificationValidator#requires()}.
+ * Mutating the provided arguments via any of the visitXxx or accept calls is considered undefined behavior.
*/ public interface SpecificationValidator { diff --git a/core/src/test/java/org/neo4j/importer/v1/ConfigurationTest.java b/core/src/test/java/org/neo4j/importer/v1/ConfigurationTest.java index 2b23ef8e..c1db6253 100644 --- a/core/src/test/java/org/neo4j/importer/v1/ConfigurationTest.java +++ b/core/src/test/java/org/neo4j/importer/v1/ConfigurationTest.java @@ -17,7 +17,6 @@ package org.neo4j.importer.v1; import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; import java.util.Map; import java.util.Optional; @@ -53,9 +52,11 @@ void does_not_return_element_if_no_names_match() { } @Test - void throws_if_requested_type_does_not_match() { + void does_not_return_element_if_type_does_not_match() { Configuration configuration = new Configuration(Map.of("a", 1)); - assertThatThrownBy(() -> configuration.get(String.class, "a")).isInstanceOf(ClassCastException.class); + Optional result = configuration.get(String.class, "a"); + + assertThat(result).isEmpty(); } } diff --git a/core/src/test/java/org/neo4j/importer/v1/e2e/AdminImportIT.java b/core/src/test/java/org/neo4j/importer/v1/e2e/AdminImportIT.java index 149470ec..ddf366ff 100644 --- a/core/src/test/java/org/neo4j/importer/v1/e2e/AdminImportIT.java +++ b/core/src/test/java/org/neo4j/importer/v1/e2e/AdminImportIT.java @@ -36,6 +36,7 @@ import java.util.Map; import java.util.Optional; import java.util.function.Function; +import java.util.stream.Collectors; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.params.ParameterizedTest; @@ -106,7 +107,7 @@ void runs_import(String extension) throws Exception { importSpec.getSources().stream().collect(toMap(Source::getName, Function.identity())); File csvFolder = csvFolderPathFor("/e2e/admin-import"); var targets = importSpec.getTargets(); - var nodeTargets = targets.getNodes(); + var nodeTargets = targets.getNodes().stream().filter(Target::isActive).collect(Collectors.toList()); for (NodeTarget nodeTarget : nodeTargets) { var source = sources.get(nodeTarget.getSource()); assertThat(source).isNotNull(); @@ -114,7 +115,9 @@ void runs_import(String extension) throws Exception { // note: source transformations are ignored, query is directly read from the source Neo4jAdmin.writeData(csvFolder, nodeTarget, SourceExecutor.read(source)); } - for (RelationshipTarget relationshipTarget : targets.getRelationships()) { + var relationshipTargets = + targets.getRelationships().stream().filter(Target::isActive).collect(Collectors.toList()); + for (RelationshipTarget relationshipTarget : relationshipTargets) { var source = sources.get(relationshipTarget.getSource()); assertThat(source).isNotNull(); Neo4jAdmin.writeHeader(csvFolder, relationshipTarget, nodeTargets); diff --git a/examples/neo4j-admin/src/test/java/org/neo4j/importer/Neo4jAdminExampleIT.java b/examples/neo4j-admin/src/test/java/org/neo4j/importer/Neo4jAdminExampleIT.java index edcbcc1f..cd74cb3a 100644 --- a/examples/neo4j-admin/src/test/java/org/neo4j/importer/Neo4jAdminExampleIT.java +++ b/examples/neo4j-admin/src/test/java/org/neo4j/importer/Neo4jAdminExampleIT.java @@ -323,7 +323,7 @@ private void createHeaderFiles(ImportSpecification specification) throws Excepti specification.getSources().stream().collect(Collectors.toMap(Source::getName, Function.identity())); Map indexedNodes = specification.getTargets().getNodes().stream() .collect(Collectors.toMap(Target::getName, Function.identity())); - for (Target target : specification.getTargets().getAll()) { + for (Target target : specification.getTargets().getAllActive()) { switch (target) { case NodeTarget nodeTarget -> createHeaderFile(indexedSources, nodeTarget); case RelationshipTarget relationshipTarget -> @@ -335,7 +335,7 @@ private void createHeaderFiles(ImportSpecification specification) throws Excepti private void createSchemaFile(ImportSpecification specification) throws IOException { var schemaStatements = - generateSchemaStatements(specification.getTargets().getAll()); + generateSchemaStatements(specification.getTargets().getAllActive()); if (schemaStatements.isEmpty()) { return; } @@ -407,7 +407,7 @@ private static String[] importCommand(ImportSpecification specification, String command.append("%s".formatted(sourceUri(specification, relationshipTarget))); } - if (targets.getAll().stream().anyMatch(Neo4jAdminExampleIT::hasSchemaOps)) { + if (targets.getAllActive().stream().anyMatch(Neo4jAdminExampleIT::hasSchemaOps)) { command.append(" --schema=/import/schema.cypher"); } From 2f05d4e10c9bb67d1d888f818f7e5ffe466edbd2 Mon Sep 17 00:00:00 2001 From: Florent Biville <445792+fbiville@users.noreply.github.com> Date: Mon, 10 Nov 2025 13:35:50 +0100 Subject: [PATCH 2/4] Update core/src/main/java/org/neo4j/importer/v1/Configuration.java --- core/src/main/java/org/neo4j/importer/v1/Configuration.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/main/java/org/neo4j/importer/v1/Configuration.java b/core/src/main/java/org/neo4j/importer/v1/Configuration.java index 0be33f05..9a521661 100644 --- a/core/src/main/java/org/neo4j/importer/v1/Configuration.java +++ b/core/src/main/java/org/neo4j/importer/v1/Configuration.java @@ -37,7 +37,7 @@ public Configuration(Map settings) { } /** - * Returns the setting by its primary name (or its alternate names), if it conforms to the provided type + * Returns the setting matched by its primary name (or its alternate names), if it conforms to the provided type * @param type expected value type of the settings * @param name primary setting name * @param alternativeNames alternative setting names, if any From b751a8ff86b3d891ba29393b0d51a133c6293a6d Mon Sep 17 00:00:00 2001 From: Florent Biville <445792+fbiville@users.noreply.github.com> Date: Mon, 10 Nov 2025 13:38:23 +0100 Subject: [PATCH 3/4] Update core/src/main/java/org/neo4j/importer/v1/Configuration.java --- core/src/main/java/org/neo4j/importer/v1/Configuration.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/main/java/org/neo4j/importer/v1/Configuration.java b/core/src/main/java/org/neo4j/importer/v1/Configuration.java index 9a521661..0454d600 100644 --- a/core/src/main/java/org/neo4j/importer/v1/Configuration.java +++ b/core/src/main/java/org/neo4j/importer/v1/Configuration.java @@ -38,7 +38,7 @@ public Configuration(Map settings) { /** * Returns the setting matched by its primary name (or its alternate names), if it conforms to the provided type - * @param type expected value type of the settings + * @param type expected value type of the setting * @param name primary setting name * @param alternativeNames alternative setting names, if any * @return the setting value if it matches the provided parameters in an optional wrapper, or an empty optional From b42929d0c416f2866defbe4adebb72fcf2710610 Mon Sep 17 00:00:00 2001 From: Florent Biville Date: Mon, 10 Nov 2025 13:48:53 +0100 Subject: [PATCH 4/4] docs: add missing docs on distribution and improve deserializer's --- .../v1/ImportSpecificationDeserializer.java | 20 ++-- .../v1/distribution/Neo4jDistribution.java | 92 +++++++++++++++++-- 2 files changed, 98 insertions(+), 14 deletions(-) diff --git a/core/src/main/java/org/neo4j/importer/v1/ImportSpecificationDeserializer.java b/core/src/main/java/org/neo4j/importer/v1/ImportSpecificationDeserializer.java index c076449b..c63557c3 100644 --- a/core/src/main/java/org/neo4j/importer/v1/ImportSpecificationDeserializer.java +++ b/core/src/main/java/org/neo4j/importer/v1/ImportSpecificationDeserializer.java @@ -60,15 +60,13 @@ public class ImportSpecificationDeserializer { .getSchema(ImportSpecificationDeserializer.class.getResourceAsStream("/spec.v1.json")); /** - * Returns an instance of {@link ImportSpecification} based on the provided {@link Reader} content. - * The result is guaranteed to be consistent with the specification JSON schema. + * Returns an instance of {@link ImportSpecification} based on the provided {@link Reader} content.
+ * The result is guaranteed to be consistent with the specification JSON schema.
*
- * If implementations of the {@link SpecificationValidator} SPI are provided, they will also run against the - * {@link ImportSpecification} instance before the latter is returned. - *
- * If the parsing, deserialization or validation (standard or via SPI implementations) fail, a {@link SpecificationException} - * is going to be thrown. - * + * If implementations of the {@link SpecificationValidator} Service Provider Interface are provided, they will also + * run against the {@link ImportSpecification} instance before the latter is returned.
+ * If the parsing, deserialization or validation (standard or via SPI implementations) fail, a + * {@link SpecificationException} is thrown. * @return an {@link ImportSpecification} * @throws SpecificationException if parsing, deserialization or validation fail */ @@ -76,6 +74,12 @@ public static ImportSpecification deserialize(Reader spec) throws SpecificationE return deserialize(spec, Optional.empty()); } + /** + * Same as {@link ImportSpecificationDeserializer#deserialize(Reader)}, except it checks extra validation rules + * against the provided {@link Neo4jDistribution} value. + * @return an {@link ImportSpecification} + * @throws SpecificationException if parsing, deserialization or validation fail + */ public static ImportSpecification deserialize(Reader spec, Neo4jDistribution neo4jDistribution) throws SpecificationException { diff --git a/core/src/main/java/org/neo4j/importer/v1/distribution/Neo4jDistribution.java b/core/src/main/java/org/neo4j/importer/v1/distribution/Neo4jDistribution.java index abbfbd75..5dc77159 100644 --- a/core/src/main/java/org/neo4j/importer/v1/distribution/Neo4jDistribution.java +++ b/core/src/main/java/org/neo4j/importer/v1/distribution/Neo4jDistribution.java @@ -19,13 +19,12 @@ import java.util.Objects; /** - * Represents a specific distribution of Neo4j, encapsulating the version and edition of the database. - * This class provides methods to check for the availability of various features and constraints - * based on the edition and version of the Neo4j distribution. - *

+ * {@link Neo4jDistribution} represents the target distribution of Neo4j, encapsulating its version and edition
+ * {@link Neo4jDistribution} class provides methods to check for the availability of various features. + *
* Assumptions: - * - Neo4j 4.4 is considered the minimum supported version. Therefore, certain features (like NodeRangeIndexes, NodePointIndexes etc.) - * are assumed to be always present in all supported versions. + * - Neo4j 4.4 is considered the minimum supported version. Therefore, certain features (like NodeRangeIndexes, + * NodePointIndexes etc.) are assumed to be always present */ public class Neo4jDistribution { private final String versionString; @@ -38,82 +37,163 @@ public class Neo4jDistribution { this.versionString = String.format("Neo4j %s %s", version, edition); } + /** + * Whether this database version is larger than or equal to the provided raw version + * @param versionString raw Neo4j version + * @return true if this database version is larger than or equal to the provided raw version, false otherwise + */ public boolean isVersionLargerThanOrEqual(String versionString) { return version.isLargerThanOrEqual(versionString); } + /** + * Whether this database edition is enterprise + * @return true if this database edition is enterprise, false otherwise + */ public boolean isEnterprise() { return edition != Neo4jDistributions.Edition.COMMUNITY; } + /** + * Whether this database supports node property type constraints + * @return true if this database supports node property type constraints, false otherwise + */ public boolean hasNodeTypeConstraints() { return isEnterprise() && version.isLargerThanOrEqual("5.9"); } + /** + * Whether this database supports node key constraints + * @return true if this database supports node key constraints, false otherwise + */ public boolean hasNodeKeyConstraints() { return isEnterprise(); } + /** + * Whether this database supports node unique constraints + * @return true if this database supports node unique constraints, false otherwise + */ public boolean hasNodeUniqueConstraints() { return true; } + /** + * Whether this database supports node property existence constraints + * @return true if this database supports node property existence constraints, false otherwise + */ public boolean hasNodeExistenceConstraints() { return isEnterprise(); } + /** + * Whether this database supports node range indexes + * @return true if this database supports node range indexes, false otherwise + */ public boolean hasNodeRangeIndexes() { return true; } + /** + * Whether this database supports node text indexes + * @return true if this database supports node text indexes, false otherwise + */ public boolean hasNodeTextIndexes() { return true; } + /** + * Whether this database supports node point indexes + * @return true if this database supports node point indexes, false otherwise + */ public boolean hasNodePointIndexes() { return true; } + /** + * Whether this database supports node full-text indexes + * @return true if this database supports node full-text indexes, false otherwise + */ public boolean hasNodeFullTextIndexes() { return true; } + /** + * Whether this database supports node vector indexes + * @return true if this database supports node vector indexes, false otherwise + */ public boolean hasNodeVectorIndexes() { return version.isLargerThanOrEqual("5.13"); } + /** + * Whether this database supports relationship key constraints + * @return true if this database supports relationship key constraints, false otherwise + */ public boolean hasRelationshipKeyConstraints() { return isEnterprise() && version.isLargerThanOrEqual("5.7"); } + /** + * Whether this database supports relationship property type constraints + * @return true if this database supports relationship property type constraints, false otherwise + */ public boolean hasRelationshipTypeConstraints() { return isEnterprise() && version.isLargerThanOrEqual("5.9"); } + /** + * Whether this database supports relationship unique constraints + * @return true if this database supports relationship unique constraints, false otherwise + */ public boolean hasRelationshipUniqueConstraints() { return version.isLargerThanOrEqual("5.7"); } + /** + * Whether this database supports relationship property existence constraints + * @return true if this database supports relationship property existence constraints, false otherwise + */ public boolean hasRelationshipExistenceConstraints() { return isEnterprise(); } + /** + * Whether this database supports relationship range indexes + * @return true if this database supports relationship range indexes, false otherwise + */ public boolean hasRelationshipRangeIndexes() { return isEnterprise(); } + /** + * Whether this database supports relationship text indexes + * @return true if this database supports relationship text indexes, false otherwise + */ public boolean hasRelationshipTextIndexes() { return true; } + /** + * Whether this database supports relationship point indexes + * @return true if this database supports relationship point indexes, false otherwise + */ public boolean hasRelationshipPointIndexes() { return true; } + /** + * Whether this database supports relationship full-text indexes + * @return true if this database supports relationship full-text indexes, false otherwise + */ public boolean hasRelationshipFullTextIndexes() { return true; } + /** + * Whether this database supports relationship vector indexes + * @return true if this database supports relationship vector indexes, false otherwise + */ public boolean hasRelationshipVectorIndexes() { return version.isLargerThanOrEqual("5.13"); }