Upsert Nodes
⚠️ 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 Nodes using UpsertN
Create new nodes or update existing ones with insert-or-update semantics.
::UpsertN({properties})
ℹ️ Info
UpsertNis a traversal step that operates on an existing traversal context. The type comes from the preceding traversal (N<Type>), not from the upsert call itself. If the traversal returns existing nodes, they are updated; if no nodes are found, a new node is created.
⚠️ Warning
When using the SDKs or curling the endpoint, the query name must match what is defined in the
queries.hxfile exactly.
Example 1: Basic node upsert with properties
QUERY UpsertPerson(name: String, age: U32) =>
existing <- N<Person>::WHERE(_::{name}::EQ(name))
person <- existing::UpsertN({name: name, age: age})
RETURN person
N::Person {
INDEX name: String,
age: U32,
}
Here’s how to run the query using the SDKs or curl
from helix.client import Client
client = Client(local=True, port=6969)
# First call creates a new person
person = client.query("UpsertPerson", {
"name": "Alice",
"age": 25,
})
print("Created person:", person)
# Subsequent calls with same data update the existing person
updated = client.query("UpsertPerson", {
"name": "Alice",
"age": 26,
})
print("Updated person:", updated)
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);
// First call creates a new person
let person: serde_json::Value = client.query("UpsertPerson", &json!({
"name": "Alice",
"age": 25,
})).await?;
println!("Created person: {person:#?}");
// Subsequent calls update the existing person
let updated: serde_json::Value = client.query("UpsertPerson", &json!({
"name": "Alice",
"age": 26,
})).await?;
println!("Updated person: {updated:#?}");
Ok(())
}
package main
import (
"fmt"
"log"
"github.com/HelixDB/helix-go"
)
func main() {
client := helix.NewClient("http://localhost:6969")
// First call creates a new person
payload := map[string]any{
"name": "Alice",
"age": uint32(25),
}
var person map[string]any
if err := client.Query("UpsertPerson", helix.WithData(payload)).Scan(&person); err != nil {
log.Fatalf("UpsertPerson failed: %s", err)
}
fmt.Printf("Created person: %#v\n", person)
// Subsequent calls update the existing person
payload["age"] = uint32(26)
var updated map[string]any
if err := client.Query("UpsertPerson", helix.WithData(payload)).Scan(&updated); err != nil {
log.Fatalf("UpsertPerson failed: %s", err)
}
fmt.Printf("Updated person: %#v\n", updated)
}
import HelixDB from "helix-ts";
async function main() {
const client = new HelixDB("http://localhost:6969");
// First call creates a new person
const person = await client.query("UpsertPerson", {
name: "Alice",
age: 25,
});
console.log("Created person:", person);
// Subsequent calls update the existing person
const updated = await client.query("UpsertPerson", {
name: "Alice",
age: 26,
});
console.log("Updated person:", updated);
}
main().catch((err) => {
console.error("UpsertPerson query failed:", err);
});
# First call creates a new person
curl -X POST \
http://localhost:6969/UpsertPerson \
-H 'Content-Type: application/json' \
-d '{"name":"Alice","age":25}'
# Subsequent calls update the existing person
curl -X POST \
http://localhost:6969/UpsertPerson \
-H 'Content-Type: application/json' \
-d '{"name":"Alice","age":26}'
Example 2: Upsert from a pre-fetched node by ID
When you already have a node ID, you can fetch it directly and apply the upsert.
QUERY UpsertPersonById(id: ID, new_age: U32) =>
existing <- N<Person>(id)
person <- existing::UpsertN({age: new_age})
RETURN person
N::Person {
INDEX name: String,
age: U32,
}
Here’s how to run the query using the SDKs or curl
from helix.client import Client
client = Client(local=True, port=6969)
result = client.query("UpsertPersonById", {
"id": "<person_id>",
"new_age": 30,
})
print("Upserted person:", 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 result: serde_json::Value = client.query("UpsertPersonById", &json!({
"id": "<person_id>",
"new_age": 30,
})).await?;
println!("Upserted person: {result:#?}");
Ok(())
}
package main
import (
"fmt"
"log"
"github.com/HelixDB/helix-go"
)
func main() {
client := helix.NewClient("http://localhost:6969")
payload := map[string]any{
"id": "<person_id>",
"new_age": uint32(30),
}
var result map[string]any
if err := client.Query("UpsertPersonById", helix.WithData(payload)).Scan(&result); err != nil {
log.Fatalf("UpsertPersonById failed: %s", err)
}
fmt.Printf("Upserted person: %#v\n", result)
}
import HelixDB from "helix-ts";
async function main() {
const client = new HelixDB("http://localhost:6969");
const result = await client.query("UpsertPersonById", {
id: "<person_id>",
new_age: 30,
});
console.log("Upserted person:", result);
}
main().catch((err) => {
console.error("UpsertPersonById query failed:", err);
});
curl -X POST \
http://localhost:6969/UpsertPersonById \
-H 'Content-Type: application/json' \
-d '{"id":"<person_id>","new_age":30}'
Example 3: Upsert with WHERE filter
Find existing nodes with a WHERE clause and update or create if not found.
QUERY UpdateOrCreatePerson(name: String, new_age: U32) =>
existing <- N<Person>::WHERE(_::{name}::EQ(name))
person <- existing::UpsertN({name: name, age: new_age})
RETURN person
N::Person {
INDEX name: String,
age: U32,
}
Here’s how to run the query using the SDKs or curl
from helix.client import Client
client = Client(local=True, port=6969)
# If "Alice" exists, updates her age; otherwise creates a new person
result = client.query("UpdateOrCreatePerson", {
"name": "Alice",
"new_age": 30,
})
print("Upserted person:", 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);
// If "Alice" exists, updates her age; otherwise creates a new person
let result: serde_json::Value = client.query("UpdateOrCreatePerson", &json!({
"name": "Alice",
"new_age": 30,
})).await?;
println!("Upserted person: {result:#?}");
Ok(())
}
package main
import (
"fmt"
"log"
"github.com/HelixDB/helix-go"
)
func main() {
client := helix.NewClient("http://localhost:6969")
// If "Alice" exists, updates her age; otherwise creates a new person
payload := map[string]any{
"name": "Alice",
"new_age": uint32(30),
}
var result map[string]any
if err := client.Query("UpdateOrCreatePerson", helix.WithData(payload)).Scan(&result); err != nil {
log.Fatalf("UpdateOrCreatePerson failed: %s", err)
}
fmt.Printf("Upserted person: %#v\n", result)
}
import HelixDB from "helix-ts";
async function main() {
const client = new HelixDB("http://localhost:6969");
// If "Alice" exists, updates her age; otherwise creates a new person
const result = await client.query("UpdateOrCreatePerson", {
name: "Alice",
new_age: 30,
});
console.log("Upserted person:", result);
}
main().catch((err) => {
console.error("UpdateOrCreatePerson query failed:", err);
});
curl -X POST \
http://localhost:6969/UpdateOrCreatePerson \
-H 'Content-Type: application/json' \
-d '{"name":"Alice","new_age":30}'
How Upsert differs from Add and Update
| Operation | Behavior |
|---|---|
AddN | Always creates a new node |
UPDATE | Only modifies existing nodes (fails if node doesn’t exist) |
UpsertN | Operates on traversal context: updates if nodes found, creates if empty |
💡 Tip
When updating,
UpsertNmerges properties: it updates specified properties while preserving any existing properties that aren’t included in the upsert.
Related operations
- Create nodes - Always create new nodes with AddN
- Update nodes - Modify existing node properties
- Upsert edges - Insert-or-update edges
- Upsert vectors - Insert-or-update vectors