# Migrating from Cypher to Curiosity

If you are familiar with Cypher (the query language used by Neo4j and others), this guide will help you translate your knowledge to Curiosity's C# IQuery fluent interface.

This guide covers the query language as available in Workspace Customizations (Endpoints, Shell, AI Tools, Code Tasks, etc.). Data Connectors have access to a limited subset of this query language, optimized for referencing existing data during ingestion jobs.

While Cypher is a declarative text-based language, IQuery is a programmatic builder. However, the conceptual steps (Start -> Filter -> Traverse -> Return) are very similar.

# Basic Lookups

# 1. Get all nodes of a label

Cypher

MATCH (n:Person) RETURN n

Curiosity

Query().StartAt("Person").Emit()

# 2. Get a node by ID

Cypher

MATCH (n) WHERE id(n) = '123' RETURN n

Curiosity

Query().StartAt("Person", "123").Emit()

# 3. Get a node by Key/Property

Cypher

MATCH (n:Person {email: 'alice@example.com'}) RETURN n

Curiosity

Query().StartAt("Person", "email", "alice@example.com").Emit()
// OR
Query().StartAt("Person").WhereString("email", "alice@example.com").Emit()

# Filtering

# 4. Filter by string property

Cypher

MATCH (n:Person) WHERE n.status = 'Active' RETURN n

Curiosity

Query().StartAt("Person").WhereString("status", "Active").Emit()

# 5. Filter by numeric property

Cypher

MATCH (n:Product) WHERE n.price > 100 RETURN n

Curiosity

Query().StartAt("Product").Where(n => n.GetFloat("price") > 100).Emit()

# 6. Filter by boolean

Cypher

MATCH (n:Task) WHERE n.completed = true RETURN n

Curiosity

Query().StartAt("Task").Where(n => n.GetBool("completed")).Emit()

# 7. Filter NOT equal

Cypher

MATCH (n:Person) WHERE n.status <> 'Deleted' RETURN n

Curiosity

Query().StartAt("Person").WhereNotString("status", "Deleted").Emit()

# 8. Filter IN list

Cypher

MATCH (n:Person) WHERE n.role IN ['Admin', 'Manager'] RETURN n

Curiosity

Query().StartAt("Person").WhereString("role", new[]{"Admin", "Manager"}).Emit()

# 9. Filter by Timestamp

Cypher

MATCH (n:Log) WHERE n.timestamp >= datetime('2023-01-01') RETURN n

Curiosity

Query().StartAt("Log").WhereTimestamp(from: new Time(2023, 1, 1), to: Time.MaxValue, insideBoundary: true).Emit()

# Relationships

# 10. Simple Traversal

Cypher

MATCH (p:Person)-[:KNOWS]->(f:Person) RETURN f

Curiosity

Query().StartAt("Person").Out("Person", "KNOWS").Emit()

# 11. Traversal ignoring edge type

Cypher

MATCH (p:Person)-->(d:Department) RETURN d

Curiosity

Query().StartAt("Person").Out("Department").Emit()

# 12. Traversal ignoring target label

Cypher

MATCH (p:Person)-[:HAS_FILE]->(f) RETURN f

Curiosity

Query().StartAt("Person").Out(edgeType: "HAS_FILE").Emit()

# 13. Multi-hop Traversal

Cypher

MATCH (p:Person)-[:KNOWS*2]->(fof:Person) RETURN fof

Curiosity

Query().StartAt("Person").OutMany(levels: 2, nodeTypes: new[] { "Person" }, edgeTypes: new[] { "KNOWS" }).Emit()

# 14. Check if related to specific node

Cypher

MATCH (p:Person) WHERE (p)-[:WORKS_FOR]->(:Company {id: 'C1'}) RETURN p

Curiosity

Query().StartAt("Person").IsRelatedToVia(Node.GetUID("Company", "C1"), "WORKS_FOR").Emit()

# 15. Check if NOT related

Cypher

MATCH (p:Person) WHERE NOT (p)-[:HAS_Badge]->() RETURN p

Curiosity

Query().StartAt("Person").IsNotRelatedToVia(edgeType: "HAS_Badge").Emit()

# Advanced Logic

# 16. Union

Cypher

MATCH (n:Manager) RETURN n
UNION
MATCH (n:Director) RETURN n

Curiosity

var q1 = Query().StartAt("Manager");
var q2 = Query().StartAt("Director");
q1.Union(q2).Emit();

# 17. Intersection

Cypher

MATCH (n:Person) WHERE n.age > 30 AND n.status = 'Active' RETURN n
// (Implicit intersection of filters)

Curiosity

// Fluent chaining acts as intersection
Query().StartAt("Person").Where(n => n.GetInt("age") > 30).WhereString("status", "Active").Emit()

// Or explicit set intersection
var q1 = Query().StartAt("Person").Where(n => n.GetInt("age") > 30);
var q2 = Query().StartAt("Person").WhereString("status", "Active");
q1.Intersect(q2).Emit();

# 18. Sorting and Limiting

Cypher

MATCH (n:Product) RETURN n ORDER BY n.created DESC LIMIT 10

Curiosity

Query().StartAt("Product").SortByTimestamp(oldestFirst: false).Take(10).Emit()

# 19. Counting

Cypher

MATCH (n:Person) RETURN count(n)

Curiosity

int count = Query().StartAt("Person").Count();

# 20. Distinct

Cypher

MATCH (a)-[:FRIEND]->(b) RETURN DISTINCT b

Curiosity

Query().StartAt("Person").Out("Person", "FRIEND").Distinct().Emit()

# 21. Path Finding (Shortest Path)

Cypher

MATCH p=shortestPath((a:Person {id:'A'})-[*]-(b:Person {id:'B'})) RETURN p

Curiosity

Query().PathBetween(Node.GetUID("Person", "A"), Node.GetUID("Person", "B"))

# 22. Text Search

Cypher

MATCH (d:Document) WHERE d.content CONTAINS 'project' RETURN d

Curiosity

Query().StartSearch("Document", "content", Search.Parse("project")).Emit()

# Complex Scenarios

# 23. Vector / Similarity Search

Cypher

// Using Vector Index (syntax varies by implementation)
CALL db.index.vector.queryNodes('embeddings', 10, $queryVector)
YIELD node, score
RETURN node, score

Curiosity

Query().StartAtSimilarText("Machine Learning applications").Emit()

# 24. Grouping and Aggregation (LINQ)

Cypher

MATCH (p:Person)
RETURN p.department, count(p) as Count
// Cypher implicitly groups by non-aggregate fields (here, p.department)

Curiosity

// Curiosity queries stream results, so grouping is done in-memory via LINQ
Query().StartAt("Person")
       .Emit()
       .AsEnumerable()
       .GroupBy(n => n.GetString("department"))
       .Select(g => new { Department = g.Key, Count = g.Count() });

# 25. Custom Logic / User Defined Functions

Cypher

// Using plugins like APOC for advanced logic
RETURN apoc.date.format(timestamp(), 'ms', 'yyyy-MM-dd')

// Or using CASE statements
MATCH (p:Person)
RETURN p.name,
  CASE
    WHEN p.salary > 100000 THEN 'High'
    ELSE 'Standard'
  END as SalaryBand

Curiosity

// Use standard C# methods or local functions
string CalculateSalaryBand(Node n) {
    return n.GetFloat("salary") > 100000 ? "High" : "Standard";
}

var results = Query().StartAt("Person")
       .Emit()
       .AsEnumerable()
       .Select(n => new {
           Name = n.GetString("name"),
           SalaryBand = CalculateSalaryBand(n)
       });

# 26. Modifying Data

To modify data, you use the ITransactionQuery interface (accessed via .Tx()) or Graph methods directly for atomic operations.

# Create or Update a Node

Cypher

MERGE (p:Person {email: 'alice@example.com'})
SET p.name = 'Alice'

Curiosity

// GetOrAdd (Atomic Upsert)
var node = await Graph.GetOrAddLockedAsync("Person", "alice@example.com");
node.Set("name", "Alice");
await Graph.CommitAsync(node);

# Update Properties on Query Results

Cypher

MATCH (p:Person) WHERE p.status = 'Old'
SET p.status = 'Inactive'

Curiosity

// Update all matching nodes
await Query().StartAt("Person")
             .WhereString("status", "Old")
             .Tx()
             .Set("status", "Inactive")
             .CommitAsync();

# Create Relationships

Cypher

MATCH (p:Person {id: '1'}), (c:Company {id: 'C1'})
CREATE (p)-[:WORKS_FOR]->(c)

Curiosity

await Query().StartAt("Person", "1")
             .Tx()
             .AddUniqueEdgeTo("WORKS_FOR", "Company", "C1")
             .CommitAsync();

# Delete Nodes

Cypher

MATCH (n:Data) WHERE n.obsolete = true
DETACH DELETE n

Curiosity

await Query().StartAt("Data")
             .Where(n => n.GetBool("obsolete"))
             .Tx()
             .Delete()
             .CommitAsync();