package neo4j import ( "context" "fmt" "sync" ) // Serial number management // We use a special Marker node in Neo4j to track the next available serial number const serialCounterKey = "serial_counter" var ( serialMutex sync.Mutex ) // getNextSerial atomically increments and returns the next serial number func (n *N) getNextSerial() (uint64, error) { serialMutex.Lock() defer serialMutex.Unlock() ctx := context.Background() // Query current serial value cypher := "MATCH (m:Marker {key: $key}) RETURN m.value AS value" params := map[string]any{"key": serialCounterKey} result, err := n.ExecuteRead(ctx, cypher, params) if err != nil { return 0, fmt.Errorf("failed to query serial counter: %w", err) } var currentSerial uint64 = 1 if result.Next(ctx) { record := result.Record() if record != nil { valueRaw, found := record.Get("value") if found { if value, ok := valueRaw.(int64); ok { currentSerial = uint64(value) } } } } // Increment serial nextSerial := currentSerial + 1 // Update counter updateCypher := ` MERGE (m:Marker {key: $key}) SET m.value = $value` updateParams := map[string]any{ "key": serialCounterKey, "value": int64(nextSerial), } _, err = n.ExecuteWrite(ctx, updateCypher, updateParams) if err != nil { return 0, fmt.Errorf("failed to update serial counter: %w", err) } return currentSerial, nil } // initSerialCounter initializes the serial counter if it doesn't exist // Uses MERGE to be idempotent - safe to call multiple times func (n *N) initSerialCounter() error { ctx := context.Background() // Use MERGE with ON CREATE to initialize only if it doesn't exist // This is idempotent and avoids race conditions initCypher := ` MERGE (m:Marker {key: $key}) ON CREATE SET m.value = $value` initParams := map[string]any{ "key": serialCounterKey, "value": int64(1), } _, err := n.ExecuteWrite(ctx, initCypher, initParams) if err != nil { return fmt.Errorf("failed to initialize serial counter: %w", err) } n.Logger.Debugf("serial counter initialized/verified") return nil }