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
UpsertEis 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 forUpsertE. The order ofFromandTois 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
| Operation | Behavior |
|---|---|
AddE | Always creates a new edge (can create duplicates) |
UpsertE | Operates on traversal context: updates if edge exists between nodes, creates if not |
💡 Tip
When updating,
UpsertEmerges properties: it updates specified properties while preserving any existing properties that aren’t included in the upsert.
Related operations
- Create edges - Always create new edges with AddE
- Upsert nodes - Insert-or-update nodes
- Upsert vectors - Insert-or-update vectors