Skip to main content
Glama
orneryd

M.I.M.I.R - Multi-agent Intelligent Memory & Insight Repository

by orneryd
merge.go9.45 kB
// Package merge provides APOC merge operations. // // This package implements all apoc.merge.* functions for merging // nodes and relationships in graph operations. package merge import ( "fmt" ) // Node represents a graph node. type Node struct { ID int64 Labels []string Properties map[string]interface{} } // Relationship represents a graph relationship. type Relationship struct { ID int64 Type string StartNode int64 EndNode int64 Properties map[string]interface{} } // MergeNode merges or creates a node based on properties. // // Example: // // apoc.merge.node(['Person'], {name: 'Alice'}, {age: 30}, {}) => node func MergeNode(labels []string, identProps, onCreateProps, onMatchProps map[string]interface{}) *Node { // Search for existing node with identProps // If found, apply onMatchProps // If not found, create with identProps + onCreateProps node := &Node{ ID: generateID(), Labels: labels, Properties: make(map[string]interface{}), } // Merge all properties for k, v := range identProps { node.Properties[k] = v } for k, v := range onCreateProps { node.Properties[k] = v } return node } // NodeEager eagerly merges a node (evaluates all properties upfront). // // Example: // // apoc.merge.nodeEager(['Person'], {name: 'Alice'}, {age: 30}) => node func NodeEager(labels []string, identProps, props map[string]interface{}) *Node { return MergeNode(labels, identProps, props, nil) } // MergeRelationship merges or creates a relationship. // // Example: // // apoc.merge.relationship(start, 'KNOWS', {}, {since: 2020}, {}, end) => rel func MergeRelationship(start *Node, relType string, identProps, onCreateProps, onMatchProps map[string]interface{}, end *Node) *Relationship { rel := &Relationship{ ID: generateID(), Type: relType, StartNode: start.ID, EndNode: end.ID, Properties: make(map[string]interface{}), } // Merge all properties for k, v := range identProps { rel.Properties[k] = v } for k, v := range onCreateProps { rel.Properties[k] = v } return rel } // RelationshipEager eagerly merges a relationship. // // Example: // // apoc.merge.relationshipEager(start, 'KNOWS', {}, {since: 2020}, end) => rel func RelationshipEager(start *Node, relType string, identProps, props map[string]interface{}, end *Node) *Relationship { return MergeRelationship(start, relType, identProps, props, nil, end) } // Nodes merges multiple nodes at once. // // Example: // // apoc.merge.nodes([{labels: ['Person'], props: {name: 'Alice'}}]) => nodes func Nodes(nodeSpecs []map[string]interface{}) []*Node { nodes := make([]*Node, 0, len(nodeSpecs)) for _, spec := range nodeSpecs { labels := []string{} if l, ok := spec["labels"].([]string); ok { labels = l } props := make(map[string]interface{}) if p, ok := spec["props"].(map[string]interface{}); ok { props = p } node := MergeNode(labels, props, nil, nil) nodes = append(nodes, node) } return nodes } // Properties merges properties onto a node. // // Example: // // apoc.merge.properties(node, {age: 31, city: 'NYC'}) => updated node func Properties(node *Node, props map[string]interface{}) *Node { for k, v := range props { node.Properties[k] = v } return node } // DeepMerge deeply merges nested properties. // // Example: // // apoc.merge.deepMerge(node, {address: {city: 'NYC'}}) => updated node func DeepMerge(node *Node, props map[string]interface{}) *Node { for k, v := range props { if existing, ok := node.Properties[k]; ok { // If both are maps, merge recursively if existingMap, ok1 := existing.(map[string]interface{}); ok1 { if newMap, ok2 := v.(map[string]interface{}); ok2 { node.Properties[k] = mergeMaps(existingMap, newMap) continue } } } node.Properties[k] = v } return node } // mergeMaps recursively merges two maps. func mergeMaps(m1, m2 map[string]interface{}) map[string]interface{} { result := make(map[string]interface{}) for k, v := range m1 { result[k] = v } for k, v := range m2 { if existing, ok := result[k]; ok { if existingMap, ok1 := existing.(map[string]interface{}); ok1 { if newMap, ok2 := v.(map[string]interface{}); ok2 { result[k] = mergeMaps(existingMap, newMap) continue } } } result[k] = v } return result } // Labels merges labels onto a node. // // Example: // // apoc.merge.labels(node, ['Employee', 'Manager']) => updated node func Labels(node *Node, labels []string) *Node { for _, label := range labels { if !hasLabel(node, label) { node.Labels = append(node.Labels, label) } } return node } // hasLabel checks if a node has a label. func hasLabel(node *Node, label string) bool { for _, l := range node.Labels { if l == label { return true } } return false } // Pattern merges a pattern (nodes and relationships). // // Example: // // apoc.merge.pattern(startProps, relType, relProps, endProps) => pattern func Pattern(startProps map[string]interface{}, relType string, relProps, endProps map[string]interface{}) map[string]interface{} { startLabels := extractLabels(startProps) endLabels := extractLabels(endProps) start := MergeNode(startLabels, startProps, nil, nil) end := MergeNode(endLabels, endProps, nil, nil) rel := MergeRelationship(start, relType, relProps, nil, nil, end) return map[string]interface{}{ "start": start, "relationship": rel, "end": end, } } // extractLabels extracts labels from properties map. func extractLabels(props map[string]interface{}) []string { if labels, ok := props["_labels"].([]string); ok { return labels } return []string{} } // Batch merges multiple entities in a batch. // // Example: // // apoc.merge.batch(specs, 1000) => results func Batch(specs []map[string]interface{}, batchSize int) []interface{} { results := make([]interface{}, 0) for i := 0; i < len(specs); i += batchSize { end := i + batchSize if end > len(specs) { end = len(specs) } batch := specs[i:end] for _, spec := range batch { // Process each spec results = append(results, spec) } } return results } // Conditional merges based on a condition. // // Example: // // apoc.merge.conditional(node, condition, props) => updated node func Conditional(node *Node, condition bool, props map[string]interface{}) *Node { if condition { return Properties(node, props) } return node } // Strategy applies a merge strategy. // // Example: // // apoc.merge.strategy(node, props, 'overwrite') => updated node func Strategy(node *Node, props map[string]interface{}, strategy string) *Node { switch strategy { case "overwrite": return Properties(node, props) case "keep": // Only add properties that don't exist for k, v := range props { if _, exists := node.Properties[k]; !exists { node.Properties[k] = v } } return node case "merge": return DeepMerge(node, props) default: return Properties(node, props) } } // Conflict resolves merge conflicts. // // Example: // // apoc.merge.conflict(node, props, resolver) => updated node func Conflict(node *Node, props map[string]interface{}, resolver func(old, new interface{}) interface{}) *Node { for k, newVal := range props { if oldVal, exists := node.Properties[k]; exists { node.Properties[k] = resolver(oldVal, newVal) } else { node.Properties[k] = newVal } } return node } // Validate validates merge operations. // // Example: // // apoc.merge.validate(node, props) => {valid: true, errors: []} func Validate(node *Node, props map[string]interface{}) map[string]interface{} { errors := make([]string, 0) // Validate property types for k, v := range props { if existing, ok := node.Properties[k]; ok { if fmt.Sprintf("%T", existing) != fmt.Sprintf("%T", v) { errors = append(errors, fmt.Sprintf("Type mismatch for property %s", k)) } } } return map[string]interface{}{ "valid": len(errors) == 0, "errors": errors, } } // Preview previews merge results without applying them. // // Example: // // apoc.merge.preview(node, props) => preview func Preview(node *Node, props map[string]interface{}) map[string]interface{} { preview := &Node{ ID: node.ID, Labels: make([]string, len(node.Labels)), Properties: make(map[string]interface{}), } copy(preview.Labels, node.Labels) for k, v := range node.Properties { preview.Properties[k] = v } for k, v := range props { preview.Properties[k] = v } return map[string]interface{}{ "before": node, "after": preview, } } // Rollback rolls back a merge operation. // // Example: // // apoc.merge.rollback(node, snapshot) => restored node func Rollback(node *Node, snapshot map[string]interface{}) *Node { if props, ok := snapshot["properties"].(map[string]interface{}); ok { node.Properties = props } if labels, ok := snapshot["labels"].([]string); ok { node.Labels = labels } return node } // Snapshot creates a snapshot of a node for rollback. // // Example: // // apoc.merge.snapshot(node) => snapshot func Snapshot(node *Node) map[string]interface{} { props := make(map[string]interface{}) for k, v := range node.Properties { props[k] = v } labels := make([]string, len(node.Labels)) copy(labels, node.Labels) return map[string]interface{}{ "properties": props, "labels": labels, } } var idCounter int64 = 0 func generateID() int64 { idCounter++ return idCounter }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/orneryd/Mimir'

If you have feedback or need assistance with the MCP directory API, please join our Discord server