CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/maven-org-neo4j--neo4j

Neo4j Community Edition - The world's leading Graph Database with Cypher query language and ACID transactions.

Pending
Overview
Eval results
Files

procedures.mddocs/

Stored Procedures and Functions

Framework for creating custom Cypher-callable procedures and functions with dependency injection, supporting READ, WRITE, SCHEMA, and DBMS execution modes for extending Neo4j functionality.

Capabilities

Procedure Annotation

Annotation for declaring methods as Cypher-callable procedures with mode specification and metadata.

/**
 * Declares methods as Cypher-callable procedures
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Procedure {
    
    /**
     * Name of the procedure in Cypher (defaults to method name)
     * @return Procedure name
     */
    String name() default "";
    
    /**
     * Execution mode for the procedure
     * @return Procedure execution mode
     */
    Mode mode() default Mode.READ;
    
    /**
     * Whether this procedure is deprecated
     * @return true if deprecated
     */
    boolean deprecated() default false;
    
    /**
     * Deprecation message if deprecated
     * @return Deprecation message
     */
    String deprecatedBy() default "";
}

Usage Examples:

import org.neo4j.procedure.Procedure;
import org.neo4j.procedure.Mode;
import org.neo4j.procedure.Name;
import org.neo4j.procedure.Description;
import java.util.stream.Stream;

public class UserProcedures {
    
    @Procedure(name = "user.create", mode = Mode.WRITE)
    @Description("Create a new user with the given name and email")
    public Stream<UserResult> createUser(
            @Name("name") String name,
            @Name("email") String email) {
        
        // Create user node with transaction from context
        Node userNode = tx.createNode(Label.label("User"));
        userNode.setProperty("name", name);
        userNode.setProperty("email", email);
        userNode.setProperty("createdAt", Instant.now().toString());
        
        return Stream.of(new UserResult(userNode.getId(), name, email));
    }
    
    @Procedure(name = "user.findByEmail", mode = Mode.READ)
    @Description("Find a user by email address")
    public Stream<UserResult> findUserByEmail(@Name("email") String email) {
        
        ResourceIterable<Node> users = tx.findNodes(Label.label("User"), "email", email);
        
        return StreamSupport.stream(users.spliterator(), false)
            .map(node -> new UserResult(
                node.getId(),
                (String) node.getProperty("name"),
                (String) node.getProperty("email")
            ));
    }
    
    @Procedure(name = "user.delete", mode = Mode.WRITE)
    @Description("Delete a user and all their relationships")
    public Stream<DeleteResult> deleteUser(@Name("userId") Long userId) {
        
        Node user = tx.getNodeById(userId);
        
        // Delete all relationships
        int relationshipsDeleted = 0;
        for (Relationship rel : user.getRelationships()) {
            rel.delete();
            relationshipsDeleted++;
        }
        
        // Delete the node
        user.delete();
        
        return Stream.of(new DeleteResult(userId, relationshipsDeleted));
    }
    
    // Result classes
    public static class UserResult {
        public final Long id;
        public final String name;
        public final String email;
        
        public UserResult(Long id, String name, String email) {
            this.id = id;
            this.name = name;
            this.email = email;
        }
    }
    
    public static class DeleteResult {
        public final Long userId;
        public final int relationshipsDeleted;
        
        public DeleteResult(Long userId, int relationshipsDeleted) {
            this.userId = userId;
            this.relationshipsDeleted = relationshipsDeleted;
        }
    }
}

User Function Annotation

Annotation for declaring methods as user-defined functions callable from Cypher expressions.

/**
 * Declares methods as user-defined functions
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface UserFunction {
    
    /**
     * Name of the function in Cypher (defaults to method name)
     * @return Function name
     */
    String name() default "";
    
    /**
     * Whether this function is deprecated
     * @return true if deprecated
     */
    boolean deprecated() default false;
    
    /**
     * Deprecation message if deprecated
     * @return Deprecation message
     */
    String deprecatedBy() default "";
}

Usage Examples:

import org.neo4j.procedure.UserFunction;
import java.time.LocalDate;
import java.time.Period;
import java.util.List;
import java.util.Map;

public class UtilityFunctions {
    
    @UserFunction(name = "util.calculateAge")
    @Description("Calculate age from birth date")
    public Long calculateAge(@Name("birthDate") LocalDate birthDate) {
        if (birthDate == null) return null;
        return (long) Period.between(birthDate, LocalDate.now()).getYears();
    }
    
    @UserFunction(name = "util.formatName")
    @Description("Format a name with proper capitalization")
    public String formatName(@Name("name") String name) {
        if (name == null || name.trim().isEmpty()) return null;
        
        return Arrays.stream(name.trim().toLowerCase().split("\\s+"))
            .map(word -> word.substring(0, 1).toUpperCase() + word.substring(1))
            .collect(Collectors.joining(" "));
    }
    
    @UserFunction(name = "util.distance")
    @Description("Calculate distance between two points")
    public Double calculateDistance(
            @Name("lat1") Double lat1, @Name("lon1") Double lon1,
            @Name("lat2") Double lat2, @Name("lon2") Double lon2) {
        
        if (lat1 == null || lon1 == null || lat2 == null || lon2 == null) {
            return null;
        }
        
        // Haversine distance calculation
        double R = 6371; // Earth's radius in kilometers
        double dLat = Math.toRadians(lat2 - lat1);
        double dLon = Math.toRadians(lon2 - lon1);
        
        double a = Math.sin(dLat/2) * Math.sin(dLat/2) +
                   Math.cos(Math.toRadians(lat1)) * Math.cos(Math.toRadians(lat2)) *
                   Math.sin(dLon/2) * Math.sin(dLon/2);
        
        double c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a));
        return R * c;
    }
    
    @UserFunction(name = "util.jsonExtract")
    @Description("Extract value from JSON string by key path")
    public Object extractFromJson(@Name("json") String json, @Name("path") String path) {
        // Implementation would parse JSON and extract value by path
        // This is a simplified example
        return parseJsonPath(json, path);
    }
}

// Usage in Cypher:
// MATCH (p:Person)
// RETURN p.name, util.calculateAge(p.birthDate) as age
//
// MATCH (p1:Person), (p2:Person)
// WHERE util.distance(p1.lat, p1.lon, p2.lat, p2.lon) < 10
// RETURN p1.name, p2.name

Context Annotation

Annotation for injecting Neo4j resources into procedure and function classes.

/**
 * Inject Neo4j resources into procedure classes
 */
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Context {
}

Usage Examples:

import org.neo4j.procedure.Context;
import org.neo4j.graphdb.GraphDatabaseService;
import org.neo4j.graphdb.Transaction;
import org.neo4j.logging.Log;

public class DataAnalysisProcedures {
    
    @Context
    public GraphDatabaseService db;
    
    @Context
    public Transaction tx;
    
    @Context
    public Log log;
    
    @Procedure(name = "analysis.nodeStats", mode = Mode.READ)
    @Description("Get statistics about nodes in the database")
    public Stream<NodeStatsResult> getNodeStatistics() {
        
        log.info("Starting node statistics analysis");
        
        Map<String, Long> labelCounts = new HashMap<>();
        
        // Count nodes by label
        for (Label label : GlobalGraphOperations.at(db).getAllLabels()) {
            long count = 0;
            try (ResourceIterable<Node> nodes = tx.findNodes(label)) {
                for (Node node : nodes) {
                    count++;
                }
            }
            labelCounts.put(label.name(), count);
            log.debug("Label " + label.name() + ": " + count + " nodes");
        }
        
        return labelCounts.entrySet().stream()
            .map(entry -> new NodeStatsResult(entry.getKey(), entry.getValue()));
    }
    
    @Procedure(name = "analysis.relationshipStats", mode = Mode.READ)
    @Description("Get statistics about relationships in the database")
    public Stream<RelationshipStatsResult> getRelationshipStatistics() {
        
        Map<String, Long> typeCounts = new HashMap<>();
        
        // Count relationships by type
        for (RelationshipType type : GlobalGraphOperations.at(db).getAllRelationshipTypes()) {
            long count = 0;
            for (Relationship rel : GlobalGraphOperations.at(db).getAllRelationships()) {
                if (rel.isType(type)) {
                    count++;
                }
            }
            typeCounts.put(type.name(), count);
        }
        
        return typeCounts.entrySet().stream()
            .map(entry -> new RelationshipStatsResult(entry.getKey(), entry.getValue()));
    }
    
    public static class NodeStatsResult {
        public final String label;
        public final Long count;
        
        public NodeStatsResult(String label, Long count) {
            this.label = label;
            this.count = count;
        }
    }
    
    public static class RelationshipStatsResult {
        public final String type;
        public final Long count;
        
        public RelationshipStatsResult(String type, Long count) {
            this.type = type;
            this.count = count;
        }
    }
}

Execution Mode Enum

Enum defining the execution modes for procedures with different permission levels.

/**
 * Execution modes for procedures
 */
public enum Mode {
    /** Read-only operations that don't modify the database */
    READ,
    
    /** Operations that can modify the database data */
    WRITE,
    
    /** Operations that can modify the database schema */
    SCHEMA,
    
    /** Database management operations (system-level) */
    DBMS
}

Parameter Annotations

Annotations for documenting procedure and function parameters.

/**
 * Specify the name of a procedure/function parameter
 */
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
public @interface Name {
    /**
     * Parameter name as it appears in Cypher
     * @return Parameter name
     */
    String value();
    
    /**
     * Default value for optional parameters
     * @return Default value
     */
    String defaultValue() default "";
}

/**
 * Provide description for procedures and functions
 */
@Target({ElementType.METHOD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
public @interface Description {
    /**
     * Description text
     * @return Description
     */
    String value();
}

Advanced Procedure Examples

import org.neo4j.procedure.*;
import org.neo4j.graphdb.*;
import java.util.concurrent.TimeUnit;

public class AdvancedProcedures {
    
    @Context
    public GraphDatabaseService db;
    
    @Context
    public Transaction tx;
    
    @Context
    public Log log;
    
    @Procedure(name = "graph.batchCreate", mode = Mode.WRITE)
    @Description("Create multiple nodes and relationships in batches")
    public Stream<BatchResult> batchCreateNodes(
            @Name("nodeData") List<Map<String, Object>> nodeData,
            @Name(value = "batchSize", defaultValue = "1000") Long batchSize) {
        
        int created = 0;
        int processed = 0;
        
        for (Map<String, Object> data : nodeData) {
            String labelName = (String) data.get("label");
            Map<String, Object> properties = (Map<String, Object>) data.get("properties");
            
            Node node = tx.createNode(Label.label(labelName));
            for (Map.Entry<String, Object> prop : properties.entrySet()) {
                node.setProperty(prop.getKey(), prop.getValue());
            }
            
            created++;
            processed++;
            
            // Commit in batches to avoid memory issues
            if (processed % batchSize == 0) {
                tx.commit();
                tx = db.beginTx();
            }
        }
        
        return Stream.of(new BatchResult(created, processed));
    }
    
    @Procedure(name = "graph.shortestPath", mode = Mode.READ)
    @Description("Find shortest path between two nodes")
    public Stream<PathResult> findShortestPath(
            @Name("startNodeId") Long startId,
            @Name("endNodeId") Long endId,
            @Name(value = "relationshipTypes", defaultValue = "") List<String> relTypes,
            @Name(value = "maxDepth", defaultValue = "15") Long maxDepth) {
        
        Node startNode = tx.getNodeById(startId);
        Node endNode = tx.getNodeById(endId);
        
        PathFinder<Path> finder = GraphAlgoFactory.shortestPath(
            PathExpanders.forTypesAndDirections(
                relTypes.stream()
                    .map(RelationshipType::withName)
                    .toArray(RelationshipType[]::new)
            ),
            maxDepth.intValue()
        );
        
        Path path = finder.findSinglePath(startNode, endNode);
        
        if (path != null) {
            return Stream.of(new PathResult(path.length(), 
                StreamSupport.stream(path.nodes().spliterator(), false)
                    .map(Node::getId)
                    .collect(Collectors.toList())
            ));
        }
        
        return Stream.empty();
    }
    
    @UserFunction(name = "graph.degree")
    @Description("Get the degree of a node")
    public Long getNodeDegree(@Name("nodeId") Long nodeId) {
        try {
            Node node = tx.getNodeById(nodeId);
            return (long) node.getDegree();
        } catch (NotFoundException e) {
            return null;
        }
    }
    
    // Result classes
    public static class BatchResult {
        public final int nodesCreated;
        public final int totalProcessed;
        
        public BatchResult(int nodesCreated, int totalProcessed) {
            this.nodesCreated = nodesCreated;
            this.totalProcessed = totalProcessed;
        }
    }
    
    public static class PathResult {
        public final int length;
        public final List<Long> nodeIds;
        
        public PathResult(int length, List<Long> nodeIds) {
            this.length = length;
            this.nodeIds = nodeIds;
        }
    }
}

// Usage in Cypher:
// CALL graph.batchCreate([
//   {label: "Person", properties: {name: "Alice", age: 30}},
//   {label: "Person", properties: {name: "Bob", age: 25}}
// ], 500)
//
// CALL graph.shortestPath(123, 456, ["FRIENDS", "KNOWS"], 10)
//
// RETURN graph.degree(123) as nodeDegree

Install with Tessl CLI

npx tessl i tessl/maven-org-neo4j--neo4j

docs

configuration.md

database-management.md

events.md

graph-database.md

graph-model.md

index.md

procedures.md

query-execution.md

schema.md

spatial.md

traversal.md

tile.json