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

org.neo4j.gds.RelationshipProjection Maven / Gradle / Ivy

There is a newer version: 2.9.0
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;

import org.immutables.value.Value;
import org.jetbrains.annotations.Nullable;
import org.neo4j.gds.annotation.ValueClass;
import org.neo4j.gds.core.Aggregation;
import org.neo4j.gds.core.ConfigKeyValidation;

import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import java.util.function.Function;

import static java.util.Collections.emptyMap;
import static org.neo4j.gds.utils.StringFormatting.formatWithLocale;

@ValueClass
public abstract class RelationshipProjection extends ElementProjection {

    public static final RelationshipProjection ALL = of(PROJECT_ALL, Orientation.NATURAL);
    public static final RelationshipProjection ALL_UNDIRECTED = of(PROJECT_ALL, Orientation.UNDIRECTED);

    public abstract String type();

    @Value.Default
    public Orientation orientation() {
        return Orientation.NATURAL;
    }

    @Value.Default
    public Aggregation aggregation() {
        return Aggregation.DEFAULT;
    }

    @Value.Default
    public boolean indexInverse() {
        return false;
    }

    @Value.Default
    @Value.Parameter(false)
    @Override
    public PropertyMappings properties() {
        return super.properties();
    }

    @Value.Check
    protected void check() {
        if (orientation() == Orientation.UNDIRECTED && indexInverse()) {
            throw new IllegalArgumentException(
                "Relationship projection `" + type() + "` cannot be UNDIRECTED and inverse indexed. " +
                "Indexing the inverse orientation is only allowed for NATURAL and REVERSE."
            );
        }
    }

    /**
     * Checks if the projection defines a global aggregation that requires at least one property mapping, such as `MIN`.
     * This check is independent of the annotated check method, since we normalize projections after they have been created.
     * (see org.neo4j.gds.config.GraphProjectFromStoreConfig#withNormalizedPropertyMappings()),
     */
    public void checkAggregation() {
        check();

        if (properties().isEmpty()) {
            switch (aggregation()) {
                case COUNT:
                case SUM:
                case MIN:
                case MAX:
                    throw new IllegalArgumentException("Setting a global `" + aggregation() + "` aggregation requires at least one property mapping.");
                default:
            }
        }
    }

    @Override
    public boolean projectAll() {
        return type().equals(PROJECT_ALL);
    }

    public static final String TYPE_KEY = "type";
    public static final String ORIENTATION_KEY = "orientation";
    public static final String AGGREGATION_KEY = "aggregation";
    public static final String INDEX_INVERSE_KEY = "indexInverse";

    public static RelationshipProjection fromMap(Map map, RelationshipType relationshipType) {
        RelationshipProjection.Builder builder = RelationshipProjection.builder();
        String type = String.valueOf(map.getOrDefault(TYPE_KEY, relationshipType.name));

        validateConfigKeys(map);

        builder.type(type);
        if (map.containsKey(ORIENTATION_KEY)) {
            builder.orientation(Orientation.parse(nonEmptyString(map, ORIENTATION_KEY)));
        }
        if (map.containsKey(INDEX_INVERSE_KEY)) {
            boolean indexInverse = (boolean) map.get(INDEX_INVERSE_KEY);
            builder.indexInverse(indexInverse);
        }
        if (map.containsKey(AGGREGATION_KEY)) {
            Aggregation aggregation = Aggregation.parse(nonEmptyString(map, AGGREGATION_KEY));
            builder.aggregation(aggregation);
            return create(map, aggregation, properties -> builder.properties(properties).build());
        }
        return create(map, properties -> builder.properties(properties).build());
    }

    public static RelationshipProjection fromString(@Nullable String type) {
        return RelationshipProjection.builder().type(type).build();
    }

    public static RelationshipProjection fromObject(Object object, RelationshipType relationshipType) {
        if (object == null) {
            return ALL;
        }
        if (object instanceof String) {
            return fromString((String) object);
        }
        if (object instanceof Map) {
            var caseInsensitiveMap = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
            //noinspection unchecked
            caseInsensitiveMap.putAll((Map) object);
            return fromMap(caseInsensitiveMap, relationshipType);
        }
        throw new IllegalArgumentException(formatWithLocale(
            "Cannot construct a relationship filter out of a %s",
            object.getClass().getName()
        ));
    }

    public static RelationshipProjection of(String type, Orientation orientation) {
        return RelationshipProjection.builder().type(type).orientation(orientation).build();
    }

    public static RelationshipProjection of(String type, Aggregation aggregation) {
        return RelationshipProjection.builder().type(type).aggregation(aggregation).build();
    }

    public static RelationshipProjection of(String type, Orientation orientation, Aggregation aggregation) {
        return RelationshipProjection.builder().type(type).aggregation(aggregation).orientation(orientation).build();
    }

    public boolean isMultiGraph() {
        boolean somePropertyIsNotAggregated = properties()
            .mappings()
            .stream()
            .anyMatch(m -> Aggregation.equivalentToNone(m.aggregation()));
        return Aggregation.equivalentToNone(aggregation()) && (properties().isEmpty() || somePropertyIsNotAggregated);
    }

    @Override
    boolean includeAggregation() {
        return true;
    }

    @Override
    void writeToObject(Map value) {
        value.put(TYPE_KEY, type());
        value.put(ORIENTATION_KEY, orientation().name());
        value.put(AGGREGATION_KEY, aggregation().name());
        value.put(INDEX_INVERSE_KEY, indexInverse());
    }

    @Override
    public RelationshipProjection withAdditionalPropertyMappings(PropertyMappings mappings) {
        PropertyMappings withSameAggregation = PropertyMappings
            .builder()
            .from(mappings)
            .withDefaultAggregation(aggregation())
            .build();

        PropertyMappings newMappings = properties().mergeWith(withSameAggregation);

        return newMappings.equals(properties())
            ? ImmutableRelationshipProjection.copyOf(this)
            : ImmutableRelationshipProjection.builder().from(this).properties(newMappings).build();
    }

    public static Builder builder() {
        return new Builder();
    }

    private static RelationshipProjection create(
        Map config,
        Aggregation defaultAggregation,
        Function constructor
    ) {
        Object properties = config.getOrDefault(PROPERTIES_KEY, emptyMap());
        PropertyMappings propertyMappings = PropertyMappings.fromObject(properties, defaultAggregation);
        return constructor.apply(propertyMappings);
    }

    private static void validateConfigKeys(Map map) {
        ConfigKeyValidation.requireOnlyKeysFrom(List.of(
            TYPE_KEY,
            ORIENTATION_KEY,
            AGGREGATION_KEY,
            PROPERTIES_KEY,
            INDEX_INVERSE_KEY
        ), map.keySet());
    }

    @org.immutables.builder.Builder.AccessibleFields
    public static final class Builder extends ImmutableRelationshipProjection.Builder implements InlineProperties {

        private InlinePropertiesBuilder propertiesBuilder;

        Builder() {
        }

        @Override
        public RelationshipProjection build() {
            buildProperties();
            return super.build();
        }

        @Override
        public InlinePropertiesBuilder inlineBuilder() {
            if (propertiesBuilder == null) {
                propertiesBuilder = new InlinePropertiesBuilder(
                    () -> this.properties,
                    newProperties -> this.properties = newProperties
                );
            }
            return propertiesBuilder;
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy