Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Upsert Edges

⚠️ Warning

HelixQL is deprecated in HelixDB v2. Queries are now written with the Rust DSL and dispatched as JSON — see the Querying guide. This section is kept as a reference for legacy HelixQL projects.

For the complete documentation index optimized for AI agents, see llms.txt.

Upsert Edges using UpsertE  

Create new edges or update existing ones with insert-or-update semantics.

UpsertE({properties})::From(node1)::To(node2)

ℹ️ Info

UpsertE is a traversal step that operates on an existing traversal context. The type comes from the preceding traversal (E<Type>), not from the upsert call itself. If an edge already exists between the specified nodes, it is updated; if no edge is found, a new edge is created.

⚠️ Warning

Edge connections (::From() and ::To()) are required for UpsertE. The order of From and To is flexible. Properties are also required.

Example 1: Basic edge upsert with properties

QUERY UpsertFriendship(id1: ID, id2: ID, since: String) =>
    person1 <- N<Person>(id1)
    person2 <- N<Person>(id2)
    existing <- E<Knows>
    edge <- existing::UpsertE({since: since})::From(person1)::To(person2)
    RETURN edge

QUERY CreatePerson(name: String, age: U32) =>
    person <- AddN<Person>({name: name, age: age})
    RETURN person
N::Person {
    INDEX name: String,
    age: U32,
}

E::Knows {
    From: Person,
    To: Person,
    Properties: {
        since: String,
    }
}

Here’s how to run the query using the SDKs or curl

from helix.client import Client

client = Client(local=True, port=6969)

# Create two people
alice = client.query("CreatePerson", {
    "name": "Alice",
    "age": 25,
})[0]["person"]

bob = client.query("CreatePerson", {
    "name": "Bob",
    "age": 28,
})[0]["person"]

# First call creates the edge
result = client.query("UpsertFriendship", {
    "id1": alice["id"],
    "id2": bob["id"],
    "since": "2024-01-15",
})

print("Created edge:", result)

# Subsequent calls update the existing edge (no duplicate created)
result = client.query("UpsertFriendship", {
    "id1": alice["id"],
    "id2": bob["id"],
    "since": "2024-06-20",
})

print("Upserted edge:", result)
use helix_rs::{HelixDB, HelixDBClient};
use serde_json::json;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let client = HelixDB::new(Some("http://localhost"), Some(6969), None);

    // Create two people
    let alice: serde_json::Value = client.query("CreatePerson", &json!({
        "name": "Alice",
        "age": 25,
    })).await?;
    let alice_id = alice["person"]["id"].as_str().unwrap().to_string();

    let bob: serde_json::Value = client.query("CreatePerson", &json!({
        "name": "Bob",
        "age": 28,
    })).await?;
    let bob_id = bob["person"]["id"].as_str().unwrap().to_string();

    // First call creates the edge
    let result: serde_json::Value = client.query("UpsertFriendship", &json!({
        "id1": alice_id,
        "id2": bob_id,
        "since": "2024-01-15",
    })).await?;

    println!("Created edge: {result:#?}");

    // Subsequent calls update the existing edge
    let result: serde_json::Value = client.query("UpsertFriendship", &json!({
        "id1": alice_id,
        "id2": bob_id,
        "since": "2024-06-20",
    })).await?;

    println!("Upserted edge: {result:#?}");

    Ok(())
}
package main

import (
    "fmt"
    "log"

    "github.com/HelixDB/helix-go"
)

func main() {
    client := helix.NewClient("http://localhost:6969")

    // Create two people
    var alice map[string]any
    if err := client.Query("CreatePerson", helix.WithData(map[string]any{
        "name": "Alice",
        "age":  uint32(25),
    })).Scan(&alice); err != nil {
        log.Fatalf("CreatePerson (Alice) failed: %s", err)
    }
    aliceID := alice["person"].(map[string]any)["id"].(string)

    var bob map[string]any
    if err := client.Query("CreatePerson", helix.WithData(map[string]any{
        "name": "Bob",
        "age":  uint32(28),
    })).Scan(&bob); err != nil {
        log.Fatalf("CreatePerson (Bob) failed: %s", err)
    }
    bobID := bob["person"].(map[string]any)["id"].(string)

    // First call creates the edge
    var result map[string]any
    if err := client.Query("UpsertFriendship", helix.WithData(map[string]any{
        "id1":   aliceID,
        "id2":   bobID,
        "since": "2024-01-15",
    })).Scan(&result); err != nil {
        log.Fatalf("UpsertFriendship failed: %s", err)
    }

    fmt.Printf("Created edge: %#v\n", result)

    // Subsequent calls update the existing edge
    if err := client.Query("UpsertFriendship", helix.WithData(map[string]any{
        "id1":   aliceID,
        "id2":   bobID,
        "since": "2024-06-20",
    })).Scan(&result); err != nil {
        log.Fatalf("UpsertFriendship failed: %s", err)
    }

    fmt.Printf("Upserted edge: %#v\n", result)
}
import HelixDB from "helix-ts";

async function main() {
    const client = new HelixDB("http://localhost:6969");

    // Create two people
    const alice = await client.query("CreatePerson", {
        name: "Alice",
        age: 25,
    });
    const aliceId: string = alice.person.id;

    const bob = await client.query("CreatePerson", {
        name: "Bob",
        age: 28,
    });
    const bobId: string = bob.person.id;

    // First call creates the edge
    let result = await client.query("UpsertFriendship", {
        id1: aliceId,
        id2: bobId,
        since: "2024-01-15",
    });

    console.log("Created edge:", result);

    // Subsequent calls update the existing edge
    result = await client.query("UpsertFriendship", {
        id1: aliceId,
        id2: bobId,
        since: "2024-06-20",
    });

    console.log("Upserted edge:", result);
}

main().catch((err) => {
    console.error("UpsertFriendship query failed:", err);
});
# Create two people first
curl -X POST \
  http://localhost:6969/CreatePerson \
  -H 'Content-Type: application/json' \
  -d '{"name":"Alice","age":25}'

curl -X POST \
  http://localhost:6969/CreatePerson \
  -H 'Content-Type: application/json' \
  -d '{"name":"Bob","age":28}'

# Create or update the edge
curl -X POST \
  http://localhost:6969/UpsertFriendship \
  -H 'Content-Type: application/json' \
  -d '{"id1":"<alice_id>","id2":"<bob_id>","since":"2024-01-15"}'

Example 2: Upsert edge with multiple properties

QUERY UpsertFriendshipWithProps(id1: ID, id2: ID, since: String, strength: F32) =>
    person1 <- N<Person>(id1)
    person2 <- N<Person>(id2)
    existing <- E<Friendship>
    edge <- existing::UpsertE({since: since, strength: strength})::From(person1)::To(person2)
    RETURN edge

QUERY CreatePerson(name: String, age: U32) =>
    person <- AddN<Person>({name: name, age: age})
    RETURN person
N::Person {
    INDEX name: String,
    age: U32,
}

E::Friendship {
    From: Person,
    To: Person,
    Properties: {
        since: String,
        strength: F32,
    }
}

Here’s how to run the query using the SDKs or curl

from helix.client import Client

client = Client(local=True, port=6969)

alice = client.query("CreatePerson", {
    "name": "Alice",
    "age": 25,
})[0]["person"]

bob = client.query("CreatePerson", {
    "name": "Bob",
    "age": 28,
})[0]["person"]

# Create friendship with properties
result = client.query("UpsertFriendshipWithProps", {
    "id1": alice["id"],
    "id2": bob["id"],
    "since": "2024-01-15",
    "strength": 0.85,
})

print("Created friendship:", result)

# Update the friendship strength
result = client.query("UpsertFriendshipWithProps", {
    "id1": alice["id"],
    "id2": bob["id"],
    "since": "2024-01-15",
    "strength": 0.95,
})

print("Updated friendship:", result)
use helix_rs::{HelixDB, HelixDBClient};
use serde_json::json;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let client = HelixDB::new(Some("http://localhost"), Some(6969), None);

    let alice: serde_json::Value = client.query("CreatePerson", &json!({
        "name": "Alice",
        "age": 25,
    })).await?;
    let alice_id = alice["person"]["id"].as_str().unwrap().to_string();

    let bob: serde_json::Value = client.query("CreatePerson", &json!({
        "name": "Bob",
        "age": 28,
    })).await?;
    let bob_id = bob["person"]["id"].as_str().unwrap().to_string();

    // Create friendship with properties
    let result: serde_json::Value = client.query("UpsertFriendshipWithProps", &json!({
        "id1": alice_id,
        "id2": bob_id,
        "since": "2024-01-15",
        "strength": 0.85,
    })).await?;

    println!("Created friendship: {result:#?}");

    // Update the friendship strength
    let result: serde_json::Value = client.query("UpsertFriendshipWithProps", &json!({
        "id1": alice_id,
        "id2": bob_id,
        "since": "2024-01-15",
        "strength": 0.95,
    })).await?;

    println!("Updated friendship: {result:#?}");

    Ok(())
}
package main

import (
    "fmt"
    "log"

    "github.com/HelixDB/helix-go"
)

func main() {
    client := helix.NewClient("http://localhost:6969")

    var alice map[string]any
    if err := client.Query("CreatePerson", helix.WithData(map[string]any{
        "name": "Alice",
        "age":  uint32(25),
    })).Scan(&alice); err != nil {
        log.Fatalf("CreatePerson (Alice) failed: %s", err)
    }
    aliceID := alice["person"].(map[string]any)["id"].(string)

    var bob map[string]any
    if err := client.Query("CreatePerson", helix.WithData(map[string]any{
        "name": "Bob",
        "age":  uint32(28),
    })).Scan(&bob); err != nil {
        log.Fatalf("CreatePerson (Bob) failed: %s", err)
    }
    bobID := bob["person"].(map[string]any)["id"].(string)

    // Create friendship with properties
    var result map[string]any
    if err := client.Query("UpsertFriendshipWithProps", helix.WithData(map[string]any{
        "id1":      aliceID,
        "id2":      bobID,
        "since":    "2024-01-15",
        "strength": float32(0.85),
    })).Scan(&result); err != nil {
        log.Fatalf("UpsertFriendshipWithProps failed: %s", err)
    }

    fmt.Printf("Created friendship: %#v\n", result)

    // Update the friendship strength
    if err := client.Query("UpsertFriendshipWithProps", helix.WithData(map[string]any{
        "id1":      aliceID,
        "id2":      bobID,
        "since":    "2024-01-15",
        "strength": float32(0.95),
    })).Scan(&result); err != nil {
        log.Fatalf("UpsertFriendshipWithProps failed: %s", err)
    }

    fmt.Printf("Updated friendship: %#v\n", result)
}
import HelixDB from "helix-ts";

async function main() {
    const client = new HelixDB("http://localhost:6969");

    const alice = await client.query("CreatePerson", {
        name: "Alice",
        age: 25,
    });
    const aliceId: string = alice.person.id;

    const bob = await client.query("CreatePerson", {
        name: "Bob",
        age: 28,
    });
    const bobId: string = bob.person.id;

    // Create friendship with properties
    let result = await client.query("UpsertFriendshipWithProps", {
        id1: aliceId,
        id2: bobId,
        since: "2024-01-15",
        strength: 0.85,
    });

    console.log("Created friendship:", result);

    // Update the friendship strength
    result = await client.query("UpsertFriendshipWithProps", {
        id1: aliceId,
        id2: bobId,
        since: "2024-01-15",
        strength: 0.95,
    });

    console.log("Updated friendship:", result);
}

main().catch((err) => {
    console.error("UpsertFriendshipWithProps query failed:", err);
});
# Create two people first
curl -X POST \
  http://localhost:6969/CreatePerson \
  -H 'Content-Type: application/json' \
  -d '{"name":"Alice","age":25}'

curl -X POST \
  http://localhost:6969/CreatePerson \
  -H 'Content-Type: application/json' \
  -d '{"name":"Bob","age":28}'

# Create friendship with properties
curl -X POST \
  http://localhost:6969/UpsertFriendshipWithProps \
  -H 'Content-Type: application/json' \
  -d '{"id1":"<alice_id>","id2":"<bob_id>","since":"2024-01-15","strength":0.85}'

# Update the friendship strength
curl -X POST \
  http://localhost:6969/UpsertFriendshipWithProps \
  -H 'Content-Type: application/json' \
  -d '{"id1":"<alice_id>","id2":"<bob_id>","since":"2024-01-15","strength":0.95}'

Example 3: Flexible From/To ordering

The ::From() and ::To() can be specified in either order.

QUERY UpsertEdgeToFrom(id1: ID, id2: ID, since: String) =>
    person1 <- N<Person>(id1)
    person2 <- N<Person>(id2)
    existing <- E<Knows>
    // To and From can be in either order
    edge <- existing::UpsertE({since: since})::To(person2)::From(person1)
    RETURN edge
N::Person {
    INDEX name: String,
    age: U32,
}

E::Knows {
    From: Person,
    To: Person,
    Properties: {
        since: String,
    }
}

How Upsert differs from Add

OperationBehavior
AddEAlways creates a new edge (can create duplicates)
UpsertEOperates on traversal context: updates if edge exists between nodes, creates if not

💡 Tip

When updating, UpsertE merges properties: it updates specified properties while preserving any existing properties that aren’t included in the upsert.