add cypher to cloud skills
This commit is contained in:
381
.claude/skills/cypher/references/common-mistakes.md
Normal file
381
.claude/skills/cypher/references/common-mistakes.md
Normal file
@@ -0,0 +1,381 @@
|
||||
# Common Cypher Mistakes and How to Avoid Them
|
||||
|
||||
## Clause Ordering Errors
|
||||
|
||||
### MATCH After CREATE Without WITH
|
||||
|
||||
**Error**: `Invalid input 'MATCH': expected ... WITH`
|
||||
|
||||
```cypher
|
||||
// WRONG
|
||||
CREATE (e:Event {id: $id})
|
||||
MATCH (ref:Event {id: $refId}) // ERROR!
|
||||
CREATE (e)-[:REFERENCES]->(ref)
|
||||
|
||||
// CORRECT - Use WITH to transition
|
||||
CREATE (e:Event {id: $id})
|
||||
WITH e
|
||||
MATCH (ref:Event {id: $refId})
|
||||
CREATE (e)-[:REFERENCES]->(ref)
|
||||
```
|
||||
|
||||
**Rule**: After CREATE, you must use WITH before MATCH.
|
||||
|
||||
### WHERE After WITH Without Carrying Variables
|
||||
|
||||
**Error**: `Variable 'x' not defined`
|
||||
|
||||
```cypher
|
||||
// WRONG - 'a' is lost
|
||||
MATCH (a:Author), (e:Event)
|
||||
WITH e
|
||||
WHERE a.pubkey = $pubkey // ERROR: 'a' not in scope
|
||||
|
||||
// CORRECT - Include all needed variables
|
||||
MATCH (a:Author), (e:Event)
|
||||
WITH a, e
|
||||
WHERE a.pubkey = $pubkey
|
||||
```
|
||||
|
||||
**Rule**: WITH resets the scope. Include all variables you need.
|
||||
|
||||
### ORDER BY Without Aliased Return
|
||||
|
||||
**Error**: `Invalid input 'ORDER': expected ... AS`
|
||||
|
||||
```cypher
|
||||
// WRONG in some contexts
|
||||
RETURN n.name
|
||||
ORDER BY n.name
|
||||
|
||||
// SAFER - Use alias
|
||||
RETURN n.name AS name
|
||||
ORDER BY name
|
||||
```
|
||||
|
||||
## MERGE Mistakes
|
||||
|
||||
### MERGE on Complex Pattern Creates Duplicates
|
||||
|
||||
```cypher
|
||||
// DANGEROUS - May create duplicate nodes
|
||||
MERGE (a:Person {name: 'Alice'})-[:KNOWS]->(b:Person {name: 'Bob'})
|
||||
|
||||
// CORRECT - MERGE nodes separately first
|
||||
MERGE (a:Person {name: 'Alice'})
|
||||
MERGE (b:Person {name: 'Bob'})
|
||||
MERGE (a)-[:KNOWS]->(b)
|
||||
```
|
||||
|
||||
**Rule**: MERGE simple patterns, not complex ones.
|
||||
|
||||
### MERGE Without Unique Property
|
||||
|
||||
```cypher
|
||||
// DANGEROUS - Will keep creating nodes
|
||||
MERGE (p:Person) // No unique identifier!
|
||||
SET p.name = 'Alice'
|
||||
|
||||
// CORRECT - Provide unique key
|
||||
MERGE (p:Person {email: $email})
|
||||
SET p.name = 'Alice'
|
||||
```
|
||||
|
||||
**Rule**: MERGE must have properties that uniquely identify the node.
|
||||
|
||||
### Missing ON CREATE/ON MATCH
|
||||
|
||||
```cypher
|
||||
// LOSES context of whether new or existing
|
||||
MERGE (p:Person {id: $id})
|
||||
SET p.updated_at = timestamp() // Always runs
|
||||
|
||||
// BETTER - Handle each case
|
||||
MERGE (p:Person {id: $id})
|
||||
ON CREATE SET p.created_at = timestamp()
|
||||
ON MATCH SET p.updated_at = timestamp()
|
||||
```
|
||||
|
||||
## NULL Handling Errors
|
||||
|
||||
### Comparing with NULL
|
||||
|
||||
```cypher
|
||||
// WRONG - NULL = NULL is NULL, not true
|
||||
WHERE n.email = null // Never matches!
|
||||
|
||||
// CORRECT
|
||||
WHERE n.email IS NULL
|
||||
WHERE n.email IS NOT NULL
|
||||
```
|
||||
|
||||
### NULL in Aggregations
|
||||
|
||||
```cypher
|
||||
// count(NULL) returns 0, collect(NULL) includes NULL
|
||||
MATCH (n:Person)
|
||||
OPTIONAL MATCH (n)-[:BOUGHT]->(p:Product)
|
||||
RETURN n.name, count(p) // count ignores NULL
|
||||
```
|
||||
|
||||
### NULL Propagation in Expressions
|
||||
|
||||
```cypher
|
||||
// Any operation with NULL returns NULL
|
||||
WHERE n.age + 1 > 21 // If n.age is NULL, whole expression is NULL (falsy)
|
||||
|
||||
// Handle with coalesce
|
||||
WHERE coalesce(n.age, 0) + 1 > 21
|
||||
```
|
||||
|
||||
## List and IN Clause Errors
|
||||
|
||||
### Empty List in IN
|
||||
|
||||
```cypher
|
||||
// An empty list never matches
|
||||
WHERE n.kind IN [] // Always false
|
||||
|
||||
// Check for empty list in application code before query
|
||||
// Or use CASE:
|
||||
WHERE CASE WHEN size($kinds) > 0 THEN n.kind IN $kinds ELSE true END
|
||||
```
|
||||
|
||||
### IN with NULL Values
|
||||
|
||||
```cypher
|
||||
// NULL in the list causes issues
|
||||
WHERE n.id IN [1, NULL, 3] // NULL is never equal to anything
|
||||
|
||||
// Filter NULLs in application code
|
||||
```
|
||||
|
||||
## Relationship Pattern Errors
|
||||
|
||||
### Forgetting Direction
|
||||
|
||||
```cypher
|
||||
// WRONG - Creates both directions
|
||||
MATCH (a)-[:FOLLOWS]-(b) // Undirected!
|
||||
|
||||
// CORRECT - Specify direction
|
||||
MATCH (a)-[:FOLLOWS]->(b) // a follows b
|
||||
MATCH (a)<-[:FOLLOWS]-(b) // b follows a
|
||||
```
|
||||
|
||||
### Variable-Length Without Bounds
|
||||
|
||||
```cypher
|
||||
// DANGEROUS - Potentially explosive
|
||||
MATCH (a)-[*]->(b) // Any length path!
|
||||
|
||||
// SAFE - Set bounds
|
||||
MATCH (a)-[*1..3]->(b) // 1 to 3 hops max
|
||||
```
|
||||
|
||||
### Creating Duplicate Relationships
|
||||
|
||||
```cypher
|
||||
// May create duplicates
|
||||
CREATE (a)-[:KNOWS]->(b)
|
||||
|
||||
// Idempotent
|
||||
MERGE (a)-[:KNOWS]->(b)
|
||||
```
|
||||
|
||||
## Performance Mistakes
|
||||
|
||||
### Cartesian Products
|
||||
|
||||
```cypher
|
||||
// WRONG - Cartesian product
|
||||
MATCH (a:Person), (b:Product)
|
||||
WHERE a.id = $personId AND b.id = $productId
|
||||
CREATE (a)-[:BOUGHT]->(b)
|
||||
|
||||
// CORRECT - Single pattern or sequential
|
||||
MATCH (a:Person {id: $personId})
|
||||
MATCH (b:Product {id: $productId})
|
||||
CREATE (a)-[:BOUGHT]->(b)
|
||||
```
|
||||
|
||||
### Late Filtering
|
||||
|
||||
```cypher
|
||||
// SLOW - Filters after collecting everything
|
||||
MATCH (e:Event)
|
||||
WITH e
|
||||
WHERE e.kind = 1 // Should be in MATCH or right after
|
||||
|
||||
// FAST - Filter early
|
||||
MATCH (e:Event)
|
||||
WHERE e.kind = 1
|
||||
```
|
||||
|
||||
### Missing LIMIT with ORDER BY
|
||||
|
||||
```cypher
|
||||
// SLOW - Sorts all results
|
||||
MATCH (e:Event)
|
||||
RETURN e
|
||||
ORDER BY e.created_at DESC
|
||||
|
||||
// FAST - Limits result set
|
||||
MATCH (e:Event)
|
||||
RETURN e
|
||||
ORDER BY e.created_at DESC
|
||||
LIMIT 100
|
||||
```
|
||||
|
||||
### Unparameterized Queries
|
||||
|
||||
```cypher
|
||||
// WRONG - No query plan caching, injection risk
|
||||
MATCH (e:Event {id: '" + eventId + "'})
|
||||
|
||||
// CORRECT - Use parameters
|
||||
MATCH (e:Event {id: $eventId})
|
||||
```
|
||||
|
||||
## String Comparison Errors
|
||||
|
||||
### Case Sensitivity
|
||||
|
||||
```cypher
|
||||
// Cypher strings are case-sensitive
|
||||
WHERE n.name = 'alice' // Won't match 'Alice'
|
||||
|
||||
// Use toLower/toUpper for case-insensitive
|
||||
WHERE toLower(n.name) = toLower($name)
|
||||
|
||||
// Or use regex with (?i)
|
||||
WHERE n.name =~ '(?i)alice'
|
||||
```
|
||||
|
||||
### LIKE vs CONTAINS
|
||||
|
||||
```cypher
|
||||
// There's no LIKE in Cypher
|
||||
WHERE n.name LIKE '%alice%' // ERROR!
|
||||
|
||||
// Use CONTAINS, STARTS WITH, ENDS WITH
|
||||
WHERE n.name CONTAINS 'alice'
|
||||
WHERE n.name STARTS WITH 'ali'
|
||||
WHERE n.name ENDS WITH 'ice'
|
||||
|
||||
// Or regex for complex patterns
|
||||
WHERE n.name =~ '.*ali.*ce.*'
|
||||
```
|
||||
|
||||
## Index Mistakes
|
||||
|
||||
### Constraint vs Index
|
||||
|
||||
```cypher
|
||||
// Constraint (also creates index, enforces uniqueness)
|
||||
CREATE CONSTRAINT foo IF NOT EXISTS FOR (n:Node) REQUIRE n.id IS UNIQUE
|
||||
|
||||
// Index only (no uniqueness enforcement)
|
||||
CREATE INDEX bar IF NOT EXISTS FOR (n:Node) ON (n.id)
|
||||
```
|
||||
|
||||
### Index Not Used
|
||||
|
||||
```cypher
|
||||
// Index on n.id won't help here
|
||||
WHERE toLower(n.id) = $id // Function applied to indexed property!
|
||||
|
||||
// Store lowercase if needed, or create computed property
|
||||
```
|
||||
|
||||
### Wrong Composite Index Order
|
||||
|
||||
```cypher
|
||||
// Index on (kind, created_at) won't help query by created_at alone
|
||||
MATCH (e:Event) WHERE e.created_at > $since // Index not used
|
||||
|
||||
// Either create single-property index or query by kind too
|
||||
CREATE INDEX event_created_at FOR (e:Event) ON (e.created_at)
|
||||
```
|
||||
|
||||
## Transaction Errors
|
||||
|
||||
### Read After Write in Same Transaction
|
||||
|
||||
```cypher
|
||||
// In Neo4j, reads in a transaction see the writes
|
||||
// But be careful with external processes
|
||||
CREATE (n:Node {id: 'new'})
|
||||
WITH n
|
||||
MATCH (m:Node {id: 'new'}) // Will find 'n'
|
||||
```
|
||||
|
||||
### Locks and Deadlocks
|
||||
|
||||
```cypher
|
||||
// MERGE takes locks; avoid complex patterns that might deadlock
|
||||
// Bad: two MERGEs on same labels in different order
|
||||
Session 1: MERGE (a:Person {id: 1}) MERGE (b:Person {id: 2})
|
||||
Session 2: MERGE (b:Person {id: 2}) MERGE (a:Person {id: 1}) // Potential deadlock
|
||||
|
||||
// Good: consistent ordering
|
||||
Session 1: MERGE (a:Person {id: 1}) MERGE (b:Person {id: 2})
|
||||
Session 2: MERGE (a:Person {id: 1}) MERGE (b:Person {id: 2})
|
||||
```
|
||||
|
||||
## Type Coercion Issues
|
||||
|
||||
### Integer vs String
|
||||
|
||||
```cypher
|
||||
// Types must match
|
||||
WHERE n.id = 123 // Won't match if n.id is "123"
|
||||
WHERE n.id = '123' // Won't match if n.id is 123
|
||||
|
||||
// Use appropriate parameter types from Go
|
||||
params["id"] = int64(123) // For integer
|
||||
params["id"] = "123" // For string
|
||||
```
|
||||
|
||||
### Boolean Handling
|
||||
|
||||
```cypher
|
||||
// Neo4j booleans vs strings
|
||||
WHERE n.active = true // Boolean
|
||||
WHERE n.active = 'true' // String - different!
|
||||
```
|
||||
|
||||
## Delete Errors
|
||||
|
||||
### Delete Node With Relationships
|
||||
|
||||
```cypher
|
||||
// ERROR - Node still has relationships
|
||||
MATCH (n:Person {id: $id})
|
||||
DELETE n
|
||||
|
||||
// CORRECT - Delete relationships first
|
||||
MATCH (n:Person {id: $id})
|
||||
DETACH DELETE n
|
||||
```
|
||||
|
||||
### Optional Match and Delete
|
||||
|
||||
```cypher
|
||||
// WRONG - DELETE NULL causes no error but also doesn't help
|
||||
OPTIONAL MATCH (n:Node {id: $id})
|
||||
DELETE n // If n is NULL, nothing happens silently
|
||||
|
||||
// Better - Check existence first or handle in application
|
||||
MATCH (n:Node {id: $id})
|
||||
DELETE n
|
||||
```
|
||||
|
||||
## Debugging Tips
|
||||
|
||||
1. **Use EXPLAIN** to see query plan without executing
|
||||
2. **Use PROFILE** to see actual execution metrics
|
||||
3. **Break complex queries** into smaller parts to isolate issues
|
||||
4. **Check parameter types** - mismatched types are a common issue
|
||||
5. **Verify indexes exist** with `SHOW INDEXES`
|
||||
6. **Check constraints** with `SHOW CONSTRAINTS`
|
||||
397
.claude/skills/cypher/references/common-patterns.md
Normal file
397
.claude/skills/cypher/references/common-patterns.md
Normal file
@@ -0,0 +1,397 @@
|
||||
# Common Cypher Patterns for ORLY Nostr Relay
|
||||
|
||||
This reference contains project-specific Cypher patterns used in the ORLY Nostr relay's Neo4j backend.
|
||||
|
||||
## Schema Overview
|
||||
|
||||
### Node Types
|
||||
|
||||
| Label | Purpose | Key Properties |
|
||||
|-------|---------|----------------|
|
||||
| `Event` | Nostr events (NIP-01) | `id`, `kind`, `pubkey`, `created_at`, `content`, `sig`, `tags`, `serial` |
|
||||
| `Author` | Event authors (for NIP-01 queries) | `pubkey` |
|
||||
| `Tag` | Generic tags | `type`, `value` |
|
||||
| `NostrUser` | Social graph users (WoT) | `pubkey`, `name`, `about`, `picture`, `nip05` |
|
||||
| `ProcessedSocialEvent` | Social event tracking | `event_id`, `event_kind`, `pubkey`, `superseded_by` |
|
||||
| `Marker` | Internal state markers | `key`, `value` |
|
||||
|
||||
### Relationship Types
|
||||
|
||||
| Type | From | To | Purpose |
|
||||
|------|------|-----|---------|
|
||||
| `AUTHORED_BY` | Event | Author | Links event to author |
|
||||
| `TAGGED_WITH` | Event | Tag | Links event to tags |
|
||||
| `REFERENCES` | Event | Event | e-tag references |
|
||||
| `MENTIONS` | Event | Author | p-tag mentions |
|
||||
| `FOLLOWS` | NostrUser | NostrUser | Contact list (kind 3) |
|
||||
| `MUTES` | NostrUser | NostrUser | Mute list (kind 10000) |
|
||||
| `REPORTS` | NostrUser | NostrUser | Reports (kind 1984) |
|
||||
|
||||
## Event Storage Patterns
|
||||
|
||||
### Create Event with Full Relationships
|
||||
|
||||
This pattern creates an event and all related nodes/relationships atomically:
|
||||
|
||||
```cypher
|
||||
// 1. Create or get author
|
||||
MERGE (a:Author {pubkey: $pubkey})
|
||||
|
||||
// 2. Create event node
|
||||
CREATE (e:Event {
|
||||
id: $eventId,
|
||||
serial: $serial,
|
||||
kind: $kind,
|
||||
created_at: $createdAt,
|
||||
content: $content,
|
||||
sig: $sig,
|
||||
pubkey: $pubkey,
|
||||
tags: $tagsJson // JSON string for full tag data
|
||||
})
|
||||
|
||||
// 3. Link to author
|
||||
CREATE (e)-[:AUTHORED_BY]->(a)
|
||||
|
||||
// 4. Process e-tags (event references)
|
||||
WITH e, a
|
||||
OPTIONAL MATCH (ref0:Event {id: $eTag_0})
|
||||
FOREACH (_ IN CASE WHEN ref0 IS NOT NULL THEN [1] ELSE [] END |
|
||||
CREATE (e)-[:REFERENCES]->(ref0)
|
||||
)
|
||||
|
||||
// 5. Process p-tags (mentions)
|
||||
WITH e, a
|
||||
MERGE (mentioned0:Author {pubkey: $pTag_0})
|
||||
CREATE (e)-[:MENTIONS]->(mentioned0)
|
||||
|
||||
// 6. Process other tags
|
||||
WITH e, a
|
||||
MERGE (tag0:Tag {type: $tagType_0, value: $tagValue_0})
|
||||
CREATE (e)-[:TAGGED_WITH]->(tag0)
|
||||
|
||||
RETURN e.id AS id
|
||||
```
|
||||
|
||||
### Check Event Existence
|
||||
|
||||
```cypher
|
||||
MATCH (e:Event {id: $id})
|
||||
RETURN e.id AS id
|
||||
LIMIT 1
|
||||
```
|
||||
|
||||
### Get Next Serial Number
|
||||
|
||||
```cypher
|
||||
MERGE (m:Marker {key: 'serial'})
|
||||
ON CREATE SET m.value = 1
|
||||
ON MATCH SET m.value = m.value + 1
|
||||
RETURN m.value AS serial
|
||||
```
|
||||
|
||||
## Query Patterns
|
||||
|
||||
### Basic Filter Query (NIP-01)
|
||||
|
||||
```cypher
|
||||
MATCH (e:Event)
|
||||
WHERE e.kind IN $kinds
|
||||
AND e.pubkey IN $authors
|
||||
AND e.created_at >= $since
|
||||
AND e.created_at <= $until
|
||||
RETURN e.id AS id,
|
||||
e.kind AS kind,
|
||||
e.created_at AS created_at,
|
||||
e.content AS content,
|
||||
e.sig AS sig,
|
||||
e.pubkey AS pubkey,
|
||||
e.tags AS tags,
|
||||
e.serial AS serial
|
||||
ORDER BY e.created_at DESC
|
||||
LIMIT $limit
|
||||
```
|
||||
|
||||
### Query by Event ID (with prefix support)
|
||||
|
||||
```cypher
|
||||
// Exact match
|
||||
MATCH (e:Event {id: $id})
|
||||
RETURN e
|
||||
|
||||
// Prefix match
|
||||
MATCH (e:Event)
|
||||
WHERE e.id STARTS WITH $idPrefix
|
||||
RETURN e
|
||||
```
|
||||
|
||||
### Query by Tag (#<tag> filter)
|
||||
|
||||
```cypher
|
||||
MATCH (e:Event)
|
||||
OPTIONAL MATCH (e)-[:TAGGED_WITH]->(t:Tag)
|
||||
WHERE t.type = $tagType AND t.value IN $tagValues
|
||||
RETURN DISTINCT e
|
||||
ORDER BY e.created_at DESC
|
||||
LIMIT $limit
|
||||
```
|
||||
|
||||
### Count Events
|
||||
|
||||
```cypher
|
||||
MATCH (e:Event)
|
||||
WHERE e.kind IN $kinds
|
||||
RETURN count(e) AS count
|
||||
```
|
||||
|
||||
### Query Delete Events Targeting an Event
|
||||
|
||||
```cypher
|
||||
MATCH (target:Event {id: $targetId})
|
||||
MATCH (e:Event {kind: 5})-[:REFERENCES]->(target)
|
||||
RETURN e
|
||||
ORDER BY e.created_at DESC
|
||||
```
|
||||
|
||||
### Replaceable Event Check (kinds 0, 3, 10000-19999)
|
||||
|
||||
```cypher
|
||||
MATCH (e:Event {kind: $kind, pubkey: $pubkey})
|
||||
WHERE e.created_at < $newCreatedAt
|
||||
RETURN e.serial AS serial
|
||||
ORDER BY e.created_at DESC
|
||||
```
|
||||
|
||||
### Parameterized Replaceable Event Check (kinds 30000-39999)
|
||||
|
||||
```cypher
|
||||
MATCH (e:Event {kind: $kind, pubkey: $pubkey})-[:TAGGED_WITH]->(t:Tag {type: 'd', value: $dValue})
|
||||
WHERE e.created_at < $newCreatedAt
|
||||
RETURN e.serial AS serial
|
||||
ORDER BY e.created_at DESC
|
||||
```
|
||||
|
||||
## Social Graph Patterns
|
||||
|
||||
### Update Profile (Kind 0)
|
||||
|
||||
```cypher
|
||||
MERGE (user:NostrUser {pubkey: $pubkey})
|
||||
ON CREATE SET
|
||||
user.created_at = timestamp(),
|
||||
user.first_seen_event = $event_id
|
||||
ON MATCH SET
|
||||
user.last_profile_update = $created_at
|
||||
SET
|
||||
user.name = $name,
|
||||
user.about = $about,
|
||||
user.picture = $picture,
|
||||
user.nip05 = $nip05,
|
||||
user.lud16 = $lud16,
|
||||
user.display_name = $display_name
|
||||
```
|
||||
|
||||
### Contact List Update (Kind 3) - Diff-Based
|
||||
|
||||
```cypher
|
||||
// Mark old event as superseded
|
||||
OPTIONAL MATCH (old:ProcessedSocialEvent {event_id: $old_event_id})
|
||||
SET old.superseded_by = $new_event_id
|
||||
|
||||
// Create new event tracking
|
||||
CREATE (new:ProcessedSocialEvent {
|
||||
event_id: $new_event_id,
|
||||
event_kind: 3,
|
||||
pubkey: $author_pubkey,
|
||||
created_at: $created_at,
|
||||
processed_at: timestamp(),
|
||||
relationship_count: $total_follows,
|
||||
superseded_by: null
|
||||
})
|
||||
|
||||
// Get or create author
|
||||
MERGE (author:NostrUser {pubkey: $author_pubkey})
|
||||
|
||||
// Update unchanged relationships to new event
|
||||
WITH author
|
||||
OPTIONAL MATCH (author)-[unchanged:FOLLOWS]->(followed:NostrUser)
|
||||
WHERE unchanged.created_by_event = $old_event_id
|
||||
AND NOT followed.pubkey IN $removed_follows
|
||||
SET unchanged.created_by_event = $new_event_id,
|
||||
unchanged.created_at = $created_at
|
||||
|
||||
// Remove old relationships for removed follows
|
||||
WITH author
|
||||
OPTIONAL MATCH (author)-[old_follows:FOLLOWS]->(followed:NostrUser)
|
||||
WHERE old_follows.created_by_event = $old_event_id
|
||||
AND followed.pubkey IN $removed_follows
|
||||
DELETE old_follows
|
||||
|
||||
// Create new relationships for added follows
|
||||
WITH author
|
||||
UNWIND $added_follows AS followed_pubkey
|
||||
MERGE (followed:NostrUser {pubkey: followed_pubkey})
|
||||
MERGE (author)-[new_follows:FOLLOWS]->(followed)
|
||||
ON CREATE SET
|
||||
new_follows.created_by_event = $new_event_id,
|
||||
new_follows.created_at = $created_at,
|
||||
new_follows.relay_received_at = timestamp()
|
||||
ON MATCH SET
|
||||
new_follows.created_by_event = $new_event_id,
|
||||
new_follows.created_at = $created_at
|
||||
```
|
||||
|
||||
### Create Report (Kind 1984)
|
||||
|
||||
```cypher
|
||||
// Create tracking node
|
||||
CREATE (evt:ProcessedSocialEvent {
|
||||
event_id: $event_id,
|
||||
event_kind: 1984,
|
||||
pubkey: $reporter_pubkey,
|
||||
created_at: $created_at,
|
||||
processed_at: timestamp(),
|
||||
relationship_count: 1,
|
||||
superseded_by: null
|
||||
})
|
||||
|
||||
// Create users and relationship
|
||||
MERGE (reporter:NostrUser {pubkey: $reporter_pubkey})
|
||||
MERGE (reported:NostrUser {pubkey: $reported_pubkey})
|
||||
CREATE (reporter)-[:REPORTS {
|
||||
created_by_event: $event_id,
|
||||
created_at: $created_at,
|
||||
relay_received_at: timestamp(),
|
||||
report_type: $report_type
|
||||
}]->(reported)
|
||||
```
|
||||
|
||||
### Get Latest Social Event for Pubkey
|
||||
|
||||
```cypher
|
||||
MATCH (evt:ProcessedSocialEvent {pubkey: $pubkey, event_kind: $kind})
|
||||
WHERE evt.superseded_by IS NULL
|
||||
RETURN evt.event_id AS event_id,
|
||||
evt.created_at AS created_at,
|
||||
evt.relationship_count AS relationship_count
|
||||
ORDER BY evt.created_at DESC
|
||||
LIMIT 1
|
||||
```
|
||||
|
||||
### Get Follows for Event
|
||||
|
||||
```cypher
|
||||
MATCH (author:NostrUser)-[f:FOLLOWS]->(followed:NostrUser)
|
||||
WHERE f.created_by_event = $event_id
|
||||
RETURN collect(followed.pubkey) AS pubkeys
|
||||
```
|
||||
|
||||
## WoT Query Patterns
|
||||
|
||||
### Find Mutual Follows
|
||||
|
||||
```cypher
|
||||
MATCH (a:NostrUser {pubkey: $pubkeyA})-[:FOLLOWS]->(b:NostrUser)
|
||||
WHERE (b)-[:FOLLOWS]->(a)
|
||||
RETURN b.pubkey AS mutual_friend
|
||||
```
|
||||
|
||||
### Find Followers
|
||||
|
||||
```cypher
|
||||
MATCH (follower:NostrUser)-[:FOLLOWS]->(user:NostrUser {pubkey: $pubkey})
|
||||
RETURN follower.pubkey, follower.name
|
||||
```
|
||||
|
||||
### Find Following
|
||||
|
||||
```cypher
|
||||
MATCH (user:NostrUser {pubkey: $pubkey})-[:FOLLOWS]->(following:NostrUser)
|
||||
RETURN following.pubkey, following.name
|
||||
```
|
||||
|
||||
### Hop Distance (Trust Path)
|
||||
|
||||
```cypher
|
||||
MATCH (start:NostrUser {pubkey: $startPubkey})
|
||||
MATCH (end:NostrUser {pubkey: $endPubkey})
|
||||
MATCH path = shortestPath((start)-[:FOLLOWS*..6]->(end))
|
||||
RETURN length(path) AS hops, [n IN nodes(path) | n.pubkey] AS path
|
||||
```
|
||||
|
||||
### Second-Degree Connections
|
||||
|
||||
```cypher
|
||||
MATCH (me:NostrUser {pubkey: $myPubkey})-[:FOLLOWS]->(:NostrUser)-[:FOLLOWS]->(suggested:NostrUser)
|
||||
WHERE NOT (me)-[:FOLLOWS]->(suggested)
|
||||
AND suggested.pubkey <> $myPubkey
|
||||
RETURN suggested.pubkey, count(*) AS commonFollows
|
||||
ORDER BY commonFollows DESC
|
||||
LIMIT 20
|
||||
```
|
||||
|
||||
## Schema Management Patterns
|
||||
|
||||
### Create Constraint
|
||||
|
||||
```cypher
|
||||
CREATE CONSTRAINT event_id_unique IF NOT EXISTS
|
||||
FOR (e:Event) REQUIRE e.id IS UNIQUE
|
||||
```
|
||||
|
||||
### Create Index
|
||||
|
||||
```cypher
|
||||
CREATE INDEX event_kind IF NOT EXISTS
|
||||
FOR (e:Event) ON (e.kind)
|
||||
```
|
||||
|
||||
### Create Composite Index
|
||||
|
||||
```cypher
|
||||
CREATE INDEX event_kind_created_at IF NOT EXISTS
|
||||
FOR (e:Event) ON (e.kind, e.created_at)
|
||||
```
|
||||
|
||||
### Drop All Data (Testing Only)
|
||||
|
||||
```cypher
|
||||
MATCH (n) DETACH DELETE n
|
||||
```
|
||||
|
||||
## Performance Patterns
|
||||
|
||||
### Use EXPLAIN/PROFILE
|
||||
|
||||
```cypher
|
||||
// See query plan without running
|
||||
EXPLAIN MATCH (e:Event) WHERE e.kind = 1 RETURN e
|
||||
|
||||
// Run and see actual metrics
|
||||
PROFILE MATCH (e:Event) WHERE e.kind = 1 RETURN e
|
||||
```
|
||||
|
||||
### Batch Import with UNWIND
|
||||
|
||||
```cypher
|
||||
UNWIND $events AS evt
|
||||
CREATE (e:Event {
|
||||
id: evt.id,
|
||||
kind: evt.kind,
|
||||
pubkey: evt.pubkey,
|
||||
created_at: evt.created_at,
|
||||
content: evt.content,
|
||||
sig: evt.sig,
|
||||
tags: evt.tags
|
||||
})
|
||||
```
|
||||
|
||||
### Efficient Pagination
|
||||
|
||||
```cypher
|
||||
// Use indexed ORDER BY with WHERE for cursor-based pagination
|
||||
MATCH (e:Event)
|
||||
WHERE e.kind = 1 AND e.created_at < $cursor
|
||||
RETURN e
|
||||
ORDER BY e.created_at DESC
|
||||
LIMIT 20
|
||||
```
|
||||
540
.claude/skills/cypher/references/syntax-reference.md
Normal file
540
.claude/skills/cypher/references/syntax-reference.md
Normal file
@@ -0,0 +1,540 @@
|
||||
# Cypher Syntax Reference
|
||||
|
||||
Complete syntax reference for Neo4j Cypher query language.
|
||||
|
||||
## Clause Reference
|
||||
|
||||
### Reading Clauses
|
||||
|
||||
#### MATCH
|
||||
|
||||
Finds patterns in the graph.
|
||||
|
||||
```cypher
|
||||
// Basic node match
|
||||
MATCH (n:Label)
|
||||
|
||||
// Match with properties
|
||||
MATCH (n:Label {key: value})
|
||||
|
||||
// Match relationships
|
||||
MATCH (a)-[r:RELATES_TO]->(b)
|
||||
|
||||
// Match path
|
||||
MATCH path = (a)-[*1..3]->(b)
|
||||
```
|
||||
|
||||
#### OPTIONAL MATCH
|
||||
|
||||
Like MATCH but returns NULL for non-matches (LEFT OUTER JOIN).
|
||||
|
||||
```cypher
|
||||
MATCH (a:Person)
|
||||
OPTIONAL MATCH (a)-[:KNOWS]->(b:Person)
|
||||
RETURN a.name, b.name // b.name may be NULL
|
||||
```
|
||||
|
||||
#### WHERE
|
||||
|
||||
Filters results.
|
||||
|
||||
```cypher
|
||||
// Comparison operators
|
||||
WHERE n.age > 21
|
||||
WHERE n.age >= 21
|
||||
WHERE n.age < 65
|
||||
WHERE n.age <= 65
|
||||
WHERE n.name = 'Alice'
|
||||
WHERE n.name <> 'Bob'
|
||||
|
||||
// Boolean operators
|
||||
WHERE n.age > 21 AND n.active = true
|
||||
WHERE n.age < 18 OR n.age > 65
|
||||
WHERE NOT n.deleted
|
||||
|
||||
// NULL checks
|
||||
WHERE n.email IS NULL
|
||||
WHERE n.email IS NOT NULL
|
||||
|
||||
// Pattern predicates
|
||||
WHERE (n)-[:KNOWS]->(:Person)
|
||||
WHERE NOT (n)-[:BLOCKED]->()
|
||||
WHERE exists((n)-[:FOLLOWS]->())
|
||||
|
||||
// String predicates
|
||||
WHERE n.name STARTS WITH 'A'
|
||||
WHERE n.name ENDS WITH 'son'
|
||||
WHERE n.name CONTAINS 'li'
|
||||
WHERE n.name =~ '(?i)alice.*' // Case-insensitive regex
|
||||
|
||||
// List predicates
|
||||
WHERE n.status IN ['active', 'pending']
|
||||
WHERE any(x IN n.tags WHERE x = 'important')
|
||||
WHERE all(x IN n.scores WHERE x > 50)
|
||||
WHERE none(x IN n.errors WHERE x IS NOT NULL)
|
||||
WHERE single(x IN n.items WHERE x.primary = true)
|
||||
```
|
||||
|
||||
### Writing Clauses
|
||||
|
||||
#### CREATE
|
||||
|
||||
Creates nodes and relationships.
|
||||
|
||||
```cypher
|
||||
// Create node
|
||||
CREATE (n:Label {key: value})
|
||||
|
||||
// Create multiple nodes
|
||||
CREATE (a:Person {name: 'Alice'}), (b:Person {name: 'Bob'})
|
||||
|
||||
// Create relationship
|
||||
CREATE (a)-[r:KNOWS {since: 2020}]->(b)
|
||||
|
||||
// Create path
|
||||
CREATE p = (a)-[:KNOWS]->(b)-[:KNOWS]->(c)
|
||||
```
|
||||
|
||||
#### MERGE
|
||||
|
||||
Find or create pattern. **Critical for idempotency**.
|
||||
|
||||
```cypher
|
||||
// MERGE node
|
||||
MERGE (n:Label {key: $uniqueKey})
|
||||
|
||||
// MERGE with ON CREATE / ON MATCH
|
||||
MERGE (n:Person {email: $email})
|
||||
ON CREATE SET n.created = timestamp(), n.name = $name
|
||||
ON MATCH SET n.accessed = timestamp()
|
||||
|
||||
// MERGE relationship (both nodes must exist or be in scope)
|
||||
MERGE (a)-[r:KNOWS]->(b)
|
||||
ON CREATE SET r.since = date()
|
||||
```
|
||||
|
||||
**MERGE Gotcha**: MERGE on a pattern locks the entire pattern. For relationships, MERGE each node first:
|
||||
|
||||
```cypher
|
||||
// CORRECT
|
||||
MERGE (a:Person {id: $id1})
|
||||
MERGE (b:Person {id: $id2})
|
||||
MERGE (a)-[:KNOWS]->(b)
|
||||
|
||||
// RISKY - may create duplicate nodes
|
||||
MERGE (a:Person {id: $id1})-[:KNOWS]->(b:Person {id: $id2})
|
||||
```
|
||||
|
||||
#### SET
|
||||
|
||||
Updates properties.
|
||||
|
||||
```cypher
|
||||
// Set single property
|
||||
SET n.name = 'Alice'
|
||||
|
||||
// Set multiple properties
|
||||
SET n.name = 'Alice', n.age = 30
|
||||
|
||||
// Set from map (replaces all properties)
|
||||
SET n = {name: 'Alice', age: 30}
|
||||
|
||||
// Set from map (adds/updates, keeps existing)
|
||||
SET n += {name: 'Alice'}
|
||||
|
||||
// Set label
|
||||
SET n:NewLabel
|
||||
|
||||
// Remove property
|
||||
SET n.obsolete = null
|
||||
```
|
||||
|
||||
#### DELETE / DETACH DELETE
|
||||
|
||||
Removes nodes and relationships.
|
||||
|
||||
```cypher
|
||||
// Delete relationship
|
||||
MATCH (a)-[r:KNOWS]->(b)
|
||||
DELETE r
|
||||
|
||||
// Delete node (must have no relationships)
|
||||
MATCH (n:Orphan)
|
||||
DELETE n
|
||||
|
||||
// Delete node and all relationships
|
||||
MATCH (n:Person {name: 'Bob'})
|
||||
DETACH DELETE n
|
||||
```
|
||||
|
||||
#### REMOVE
|
||||
|
||||
Removes properties and labels.
|
||||
|
||||
```cypher
|
||||
// Remove property
|
||||
REMOVE n.temporary
|
||||
|
||||
// Remove label
|
||||
REMOVE n:OldLabel
|
||||
```
|
||||
|
||||
### Projection Clauses
|
||||
|
||||
#### RETURN
|
||||
|
||||
Specifies output.
|
||||
|
||||
```cypher
|
||||
// Return nodes
|
||||
RETURN n
|
||||
|
||||
// Return properties
|
||||
RETURN n.name, n.age
|
||||
|
||||
// Return with alias
|
||||
RETURN n.name AS name, n.age AS age
|
||||
|
||||
// Return all
|
||||
RETURN *
|
||||
|
||||
// Return distinct
|
||||
RETURN DISTINCT n.category
|
||||
|
||||
// Return expression
|
||||
RETURN n.price * n.quantity AS total
|
||||
```
|
||||
|
||||
#### WITH
|
||||
|
||||
Passes results between query parts. **Critical for multi-part queries**.
|
||||
|
||||
```cypher
|
||||
// Filter and pass
|
||||
MATCH (n:Person)
|
||||
WITH n WHERE n.age > 21
|
||||
RETURN n
|
||||
|
||||
// Aggregate and continue
|
||||
MATCH (n:Person)-[:BOUGHT]->(p:Product)
|
||||
WITH n, count(p) AS purchases
|
||||
WHERE purchases > 5
|
||||
RETURN n.name, purchases
|
||||
|
||||
// Order and limit mid-query
|
||||
MATCH (n:Person)
|
||||
WITH n ORDER BY n.age DESC LIMIT 10
|
||||
MATCH (n)-[:LIVES_IN]->(c:City)
|
||||
RETURN n.name, c.name
|
||||
```
|
||||
|
||||
**WITH resets scope**: Variables not listed in WITH are no longer available.
|
||||
|
||||
#### ORDER BY
|
||||
|
||||
Sorts results.
|
||||
|
||||
```cypher
|
||||
ORDER BY n.name // Ascending (default)
|
||||
ORDER BY n.name ASC // Explicit ascending
|
||||
ORDER BY n.name DESC // Descending
|
||||
ORDER BY n.lastName, n.firstName // Multiple fields
|
||||
ORDER BY n.priority DESC, n.name // Mixed
|
||||
```
|
||||
|
||||
#### SKIP and LIMIT
|
||||
|
||||
Pagination.
|
||||
|
||||
```cypher
|
||||
// Skip first 10
|
||||
SKIP 10
|
||||
|
||||
// Return only 20
|
||||
LIMIT 20
|
||||
|
||||
// Pagination
|
||||
ORDER BY n.created_at DESC
|
||||
SKIP $offset LIMIT $pageSize
|
||||
```
|
||||
|
||||
### Sub-queries
|
||||
|
||||
#### CALL (Subquery)
|
||||
|
||||
Execute subquery for each row.
|
||||
|
||||
```cypher
|
||||
MATCH (p:Person)
|
||||
CALL {
|
||||
WITH p
|
||||
MATCH (p)-[:BOUGHT]->(prod:Product)
|
||||
RETURN count(prod) AS purchaseCount
|
||||
}
|
||||
RETURN p.name, purchaseCount
|
||||
```
|
||||
|
||||
#### UNION
|
||||
|
||||
Combine results from multiple queries.
|
||||
|
||||
```cypher
|
||||
MATCH (n:Person) RETURN n.name AS name
|
||||
UNION
|
||||
MATCH (n:Company) RETURN n.name AS name
|
||||
|
||||
// UNION ALL keeps duplicates
|
||||
MATCH (n:Person) RETURN n.name AS name
|
||||
UNION ALL
|
||||
MATCH (n:Company) RETURN n.name AS name
|
||||
```
|
||||
|
||||
### Control Flow
|
||||
|
||||
#### FOREACH
|
||||
|
||||
Iterate over list, execute updates.
|
||||
|
||||
```cypher
|
||||
// Set property on path nodes
|
||||
MATCH path = (a)-[*]->(b)
|
||||
FOREACH (n IN nodes(path) | SET n.visited = true)
|
||||
|
||||
// Conditional operation (common pattern)
|
||||
OPTIONAL MATCH (target:Node {id: $id})
|
||||
FOREACH (_ IN CASE WHEN target IS NOT NULL THEN [1] ELSE [] END |
|
||||
CREATE (source)-[:LINKS_TO]->(target)
|
||||
)
|
||||
```
|
||||
|
||||
#### CASE
|
||||
|
||||
Conditional expressions.
|
||||
|
||||
```cypher
|
||||
// Simple CASE
|
||||
RETURN CASE n.status
|
||||
WHEN 'active' THEN 'A'
|
||||
WHEN 'pending' THEN 'P'
|
||||
ELSE 'X'
|
||||
END AS code
|
||||
|
||||
// Generic CASE
|
||||
RETURN CASE
|
||||
WHEN n.age < 18 THEN 'minor'
|
||||
WHEN n.age < 65 THEN 'adult'
|
||||
ELSE 'senior'
|
||||
END AS category
|
||||
```
|
||||
|
||||
## Operators
|
||||
|
||||
### Comparison
|
||||
|
||||
| Operator | Description |
|
||||
|----------|-------------|
|
||||
| `=` | Equal |
|
||||
| `<>` | Not equal |
|
||||
| `<` | Less than |
|
||||
| `>` | Greater than |
|
||||
| `<=` | Less than or equal |
|
||||
| `>=` | Greater than or equal |
|
||||
| `IS NULL` | Is null |
|
||||
| `IS NOT NULL` | Is not null |
|
||||
|
||||
### Boolean
|
||||
|
||||
| Operator | Description |
|
||||
|----------|-------------|
|
||||
| `AND` | Logical AND |
|
||||
| `OR` | Logical OR |
|
||||
| `NOT` | Logical NOT |
|
||||
| `XOR` | Exclusive OR |
|
||||
|
||||
### String
|
||||
|
||||
| Operator | Description |
|
||||
|----------|-------------|
|
||||
| `STARTS WITH` | Prefix match |
|
||||
| `ENDS WITH` | Suffix match |
|
||||
| `CONTAINS` | Substring match |
|
||||
| `=~` | Regex match |
|
||||
|
||||
### List
|
||||
|
||||
| Operator | Description |
|
||||
|----------|-------------|
|
||||
| `IN` | List membership |
|
||||
| `+` | List concatenation |
|
||||
|
||||
### Mathematical
|
||||
|
||||
| Operator | Description |
|
||||
|----------|-------------|
|
||||
| `+` | Addition |
|
||||
| `-` | Subtraction |
|
||||
| `*` | Multiplication |
|
||||
| `/` | Division |
|
||||
| `%` | Modulo |
|
||||
| `^` | Exponentiation |
|
||||
|
||||
## Functions
|
||||
|
||||
### Aggregation
|
||||
|
||||
```cypher
|
||||
count(*) // Count rows
|
||||
count(n) // Count non-null
|
||||
count(DISTINCT n) // Count unique
|
||||
sum(n.value) // Sum
|
||||
avg(n.value) // Average
|
||||
min(n.value) // Minimum
|
||||
max(n.value) // Maximum
|
||||
collect(n) // Collect to list
|
||||
collect(DISTINCT n) // Collect unique
|
||||
stDev(n.value) // Standard deviation
|
||||
percentileCont(n.value, 0.5) // Median
|
||||
```
|
||||
|
||||
### Scalar
|
||||
|
||||
```cypher
|
||||
// Type functions
|
||||
id(n) // Internal node ID (deprecated, use elementId)
|
||||
elementId(n) // Element ID string
|
||||
labels(n) // Node labels
|
||||
type(r) // Relationship type
|
||||
properties(n) // Property map
|
||||
|
||||
// Math
|
||||
abs(x)
|
||||
ceil(x)
|
||||
floor(x)
|
||||
round(x)
|
||||
sign(x)
|
||||
sqrt(x)
|
||||
rand() // Random 0-1
|
||||
|
||||
// String
|
||||
size(str) // String length
|
||||
toLower(str)
|
||||
toUpper(str)
|
||||
trim(str)
|
||||
ltrim(str)
|
||||
rtrim(str)
|
||||
replace(str, from, to)
|
||||
substring(str, start, len)
|
||||
left(str, len)
|
||||
right(str, len)
|
||||
split(str, delimiter)
|
||||
reverse(str)
|
||||
toString(val)
|
||||
|
||||
// Null handling
|
||||
coalesce(val1, val2, ...) // First non-null
|
||||
nullIf(val1, val2) // NULL if equal
|
||||
|
||||
// Type conversion
|
||||
toInteger(val)
|
||||
toFloat(val)
|
||||
toBoolean(val)
|
||||
toString(val)
|
||||
```
|
||||
|
||||
### List Functions
|
||||
|
||||
```cypher
|
||||
size(list) // List length
|
||||
head(list) // First element
|
||||
tail(list) // All but first
|
||||
last(list) // Last element
|
||||
range(start, end) // Create range [start..end]
|
||||
range(start, end, step)
|
||||
reverse(list)
|
||||
keys(map) // Map keys as list
|
||||
values(map) // Map values as list
|
||||
|
||||
// List predicates
|
||||
any(x IN list WHERE predicate)
|
||||
all(x IN list WHERE predicate)
|
||||
none(x IN list WHERE predicate)
|
||||
single(x IN list WHERE predicate)
|
||||
|
||||
// List manipulation
|
||||
[x IN list WHERE predicate] // Filter
|
||||
[x IN list | expression] // Map
|
||||
[x IN list WHERE pred | expr] // Filter and map
|
||||
reduce(s = initial, x IN list | s + x) // Reduce
|
||||
```
|
||||
|
||||
### Path Functions
|
||||
|
||||
```cypher
|
||||
nodes(path) // Nodes in path
|
||||
relationships(path) // Relationships in path
|
||||
length(path) // Number of relationships
|
||||
shortestPath((a)-[*]-(b))
|
||||
allShortestPaths((a)-[*]-(b))
|
||||
```
|
||||
|
||||
### Temporal Functions
|
||||
|
||||
```cypher
|
||||
timestamp() // Current Unix timestamp (ms)
|
||||
datetime() // Current datetime
|
||||
date() // Current date
|
||||
time() // Current time
|
||||
duration({days: 1, hours: 12})
|
||||
|
||||
// Components
|
||||
datetime().year
|
||||
datetime().month
|
||||
datetime().day
|
||||
datetime().hour
|
||||
|
||||
// Parsing
|
||||
date('2024-01-15')
|
||||
datetime('2024-01-15T10:30:00Z')
|
||||
```
|
||||
|
||||
### Spatial Functions
|
||||
|
||||
```cypher
|
||||
point({x: 1, y: 2})
|
||||
point({latitude: 37.5, longitude: -122.4})
|
||||
distance(point1, point2)
|
||||
```
|
||||
|
||||
## Comments
|
||||
|
||||
```cypher
|
||||
// Single line comment
|
||||
|
||||
/* Multi-line
|
||||
comment */
|
||||
```
|
||||
|
||||
## Transaction Control
|
||||
|
||||
```cypher
|
||||
// In procedures/transactions
|
||||
:begin
|
||||
:commit
|
||||
:rollback
|
||||
```
|
||||
|
||||
## Parameter Syntax
|
||||
|
||||
```cypher
|
||||
// Parameter reference
|
||||
$paramName
|
||||
|
||||
// In properties
|
||||
{key: $value}
|
||||
|
||||
// In WHERE
|
||||
WHERE n.id = $id
|
||||
|
||||
// In expressions
|
||||
RETURN $multiplier * n.value
|
||||
```
|
||||
Reference in New Issue
Block a user