All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.neo4j.gds.projection.GraphImporter Maven / Gradle / Ivy

There is a newer version: 2.13.2
Show newest version
/*
 * Copyright (c) "Neo4j"
 * Neo4j Sweden AB [http://neo4j.com]
 *
 * This file is part of Neo4j.
 *
 * Neo4j is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see .
 */
package org.neo4j.gds.projection;

import org.jetbrains.annotations.Nullable;
import org.neo4j.gds.RelationshipType;
import org.neo4j.gds.api.DatabaseId;
import org.neo4j.gds.api.DefaultValue;
import org.neo4j.gds.api.PropertyState;
import org.neo4j.gds.api.compress.AdjacencyCompressor;
import org.neo4j.gds.api.schema.ImmutableMutableGraphSchema;
import org.neo4j.gds.api.schema.MutableGraphSchema;
import org.neo4j.gds.api.schema.MutableRelationshipSchema;
import org.neo4j.gds.config.GraphProjectConfig;
import org.neo4j.gds.core.Aggregation;
import org.neo4j.gds.core.loading.Capabilities.WriteMode;
import org.neo4j.gds.core.loading.GraphStoreBuilder;
import org.neo4j.gds.core.loading.GraphStoreCatalog;
import org.neo4j.gds.core.loading.ImmutableNodes;
import org.neo4j.gds.core.loading.ImmutableStaticCapabilities;
import org.neo4j.gds.core.loading.LazyIdMapBuilder;
import org.neo4j.gds.core.loading.ReadHelper;
import org.neo4j.gds.core.loading.RelationshipImportResult;
import org.neo4j.gds.core.loading.construction.GraphFactory;
import org.neo4j.gds.core.loading.construction.ImmutablePropertyConfig;
import org.neo4j.gds.core.loading.construction.NodeLabelToken;
import org.neo4j.gds.core.loading.construction.PropertyValues;
import org.neo4j.gds.core.loading.construction.RelationshipsBuilder;
import org.neo4j.gds.core.utils.ProgressTimer;
import org.neo4j.values.AnyValue;
import org.neo4j.values.storable.TextValue;
import org.neo4j.values.virtual.MapValue;

import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;

import static org.neo4j.gds.Orientation.NATURAL;
import static org.neo4j.gds.Orientation.UNDIRECTED;

public final class GraphImporter {
    public static final int NO_TARGET_NODE = -1;

    private final GraphProjectConfig config;
    private final List undirectedRelationshipTypes;
    private final List inverseIndexedRelationshipTypes;
    private final LazyIdMapBuilder idMapBuilder;

    private final WriteMode writeMode;
    private final String query;

    private final Map relImporters;
    private final ImmutableMutableGraphSchema.Builder graphSchemaBuilder;

    public GraphImporter(
        GraphProjectConfig config,
        List undirectedRelationshipTypes,
        List inverseIndexedRelationshipTypes,
        LazyIdMapBuilder idMapBuilder,
        WriteMode writeMode,
        String query
    ) {
        this.config = config;
        this.undirectedRelationshipTypes = undirectedRelationshipTypes;
        this.inverseIndexedRelationshipTypes = inverseIndexedRelationshipTypes;
        this.idMapBuilder = idMapBuilder;
        this.writeMode = writeMode;
        this.query = query;
        this.relImporters = new ConcurrentHashMap<>();
        this.graphSchemaBuilder = MutableGraphSchema.builder();
    }

    static GraphImporter of(
        TextValue graphNameValue,
        String username,
        String query,
        DatabaseId databaseId,
        AnyValue configMap,
        WriteMode writeMode,
        PropertyState propertyState
    ) {
        var graphName = graphNameValue.stringValue();

        validateGraphName(graphName, username, databaseId);
        var config = GraphProjectFromCypherAggregationConfig.of(
            username,
            graphName,
            query,
            (configMap instanceof MapValue) ? (MapValue) configMap : MapValue.EMPTY
        );

        var idMapBuilder = idMapBuilder(config.readConcurrency(), propertyState);

        return new GraphImporter(
            config,
            config.undirectedRelationshipTypes(),
            config.inverseIndexedRelationshipTypes(),
            idMapBuilder,
            writeMode,
            query
        );
    }

    private static void validateGraphName(String graphName, String username, DatabaseId databaseId) {
        if (GraphStoreCatalog.exists(username, databaseId, graphName)) {
            throw new IllegalArgumentException("Graph " + graphName + " already exists");
        }
    }

    private static LazyIdMapBuilder idMapBuilder(int readConcurrency, PropertyState propertyState) {
        return new LazyIdMapBuilder(readConcurrency, true, true, propertyState);
    }

    public void update(
        long sourceNode,
        long targetNode,
        @Nullable PropertyValues sourceNodePropertyValues,
        @Nullable PropertyValues targetNodePropertyValues,
        NodeLabelToken sourceNodeLabels,
        NodeLabelToken targetNodeLabels,
        RelationshipType relationshipType,
        @Nullable PropertyValues relationshipProperties
    ) {

        var intermediateSourceId = loadNode(sourceNode, sourceNodeLabels, sourceNodePropertyValues);

        if (targetNode != NO_TARGET_NODE) {
            RelationshipsBuilder relImporter;
            // we do the check before to avoid having to create a new lambda instance on every call
            if (this.relImporters.containsKey(relationshipType)) {
                relImporter = this.relImporters.get(relationshipType);
            } else {
                var finalRelationshipProperties = relationshipProperties;
                relImporter = this.relImporters.computeIfAbsent(
                    relationshipType,
                    type -> newRelImporter(type, finalRelationshipProperties)
                );
            }

            var intermediateTargetId = loadNode(targetNode, targetNodeLabels, targetNodePropertyValues);

            if (relationshipProperties != null) {
                if (relationshipProperties.size() == 1) {
                    relationshipProperties.forEach((key, value) -> {
                        var property = ReadHelper.extractValue(value, DefaultValue.DOUBLE_DEFAULT_FALLBACK);
                        relImporter.addFromInternal(intermediateSourceId, intermediateTargetId, property);
                    });
                } else {
                    var propertyValues = new double[relationshipProperties.size()];
                    int[] index = {0};
                    relationshipProperties.forEach((key, value) -> {
                        var property = ReadHelper.extractValue(value, DefaultValue.DOUBLE_DEFAULT_FALLBACK);
                        var i = index[0]++;
                        propertyValues[i] = property;
                    });
                    relImporter.addFromInternal(intermediateSourceId, intermediateTargetId, propertyValues);
                }
            } else {
                relImporter.addFromInternal(intermediateSourceId, intermediateTargetId);
            }
        }
    }

    public AggregationResult result(
        DatabaseId databaseId,
        ProgressTimer timer,
        boolean hasSeenArbitraryId
    ) {
        var graphName = config.graphName();

        // in case something else has written something with the same graph name
        // validate again before doing the heavier graph building
        validateGraphName(config.graphName(), config.username(), databaseId);

        this.idMapBuilder.prepareForFlush();

        var writeMode = hasSeenArbitraryId
            ? WriteMode.NONE
            : this.writeMode;

        var graphStoreBuilder = new GraphStoreBuilder()
            .concurrency(this.config.readConcurrency())
            .capabilities(ImmutableStaticCapabilities.of(writeMode))
            .databaseId(databaseId);

        var valueMapper = buildNodesWithProperties(graphStoreBuilder);
        buildRelationshipsWithProperties(graphStoreBuilder, valueMapper);

        var graphStore = graphStoreBuilder.schema(this.graphSchemaBuilder.build()).build();

        GraphStoreCatalog.set(this.config, graphStore);

        var projectMillis = timer.stop().getDuration();

        return AggregationResultImpl.builder()
            .graphName(graphName)
            .nodeCount(graphStore.nodeCount())
            .relationshipCount(graphStore.relationshipCount())
            .projectMillis(projectMillis)
            .configuration(this.config.toMap())
            .query(this.query)
            .build();
    }

    private RelationshipsBuilder newRelImporter(RelationshipType relType, @Nullable PropertyValues properties) {
        var orientation = this.undirectedRelationshipTypes.contains(relType.name) || this.undirectedRelationshipTypes.contains(
            "*")
            ? UNDIRECTED
            : NATURAL;

        boolean indexInverse = inverseIndexedRelationshipTypes.contains(relType.name)
                               || inverseIndexedRelationshipTypes.contains("*");

        var relationshipsBuilderBuilder = GraphFactory.initRelationshipsBuilder()
            .nodes(this.idMapBuilder)
            .relationshipType(relType)
            .orientation(orientation)
            .aggregation(Aggregation.NONE)
            .indexInverse(indexInverse)
            .concurrency(this.config.readConcurrency());

        if (properties != null) {
            for (String propertyKey : properties.propertyKeys()) {
                relationshipsBuilderBuilder.addPropertyConfig(
                    ImmutablePropertyConfig.builder().propertyKey(propertyKey).build()
                );
            }
        }

        return relationshipsBuilderBuilder.build();
    }

    /**
     * Adds the given node to the internal nodes builder and returns
     * the intermediate node id which can be used for relationships.
     *
     * @return intermediate node id
     */
    private long loadNode(
        long node,
        NodeLabelToken nodeLabels,
        @Nullable PropertyValues nodeProperties
    ) {
        return nodeProperties == null
            ? this.idMapBuilder.addNode(node, nodeLabels)
            : this.idMapBuilder.addNodeWithProperties(
                node,
                nodeProperties,
                nodeLabels
            );
    }

    private AdjacencyCompressor.ValueMapper buildNodesWithProperties(GraphStoreBuilder graphStoreBuilder) {
        var idMapAndProperties = this.idMapBuilder.build();

        var idMap = idMapAndProperties.idMap();
        var nodeSchema = idMapAndProperties.schema();

        this.graphSchemaBuilder.nodeSchema(nodeSchema);

        var nodes = ImmutableNodes
            .builder()
            .idMap(idMap)
            .schema(nodeSchema)
            .properties(idMapAndProperties.propertyStore())
            .build();

        graphStoreBuilder.nodes(nodes);

        // Relationships are added using their intermediate node ids.
        // In order to map to the final internal ids, we need to use
        // the mapping function of the wrapped id map.
        return idMap.rootIdMap()::toMappedNodeId;
    }

    private void buildRelationshipsWithProperties(
        GraphStoreBuilder graphStoreBuilder,
        AdjacencyCompressor.ValueMapper valueMapper
    ) {
        var relationshipImportResultBuilder = RelationshipImportResult.builder();

        var relationshipSchema = MutableRelationshipSchema.empty();
        this.relImporters.forEach((relationshipType, relImporter) -> {
            var relationships = relImporter.build(
                Optional.of(valueMapper),
                Optional.empty()
            );
            relationshipSchema.set(relationships.relationshipSchemaEntry());
            relationshipImportResultBuilder.putImportResult(relationshipType, relationships);
        });

        graphStoreBuilder.relationshipImportResult(relationshipImportResultBuilder.build());
        this.graphSchemaBuilder.relationshipSchema(relationshipSchema);

        // release all references to the builders
        // we are only be called once and don't support double invocations of `result` building
        this.relImporters.clear();
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy