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 Vectors

⚠️ 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 Vectors using UpsertV  

Create new vector embeddings or update existing ones with insert-or-update semantics.

::UpsertV(vector, {properties})

ℹ️ Info

UpsertV is a traversal step that operates on an existing traversal context. The type comes from the preceding traversal (V<Type>), not from the upsert call itself. If the traversal returns existing vectors, they are updated; if no vectors are found, a new vector is created.

⚠️ Warning

Vector data is required for UpsertV. You can provide vector data as:

  • A literal array of floats (e.g., [0.1, 0.2, 0.3])
  • The Embed() function to generate embeddings from text
  • A variable containing vector data

Example 1: Basic vector upsert with properties

QUERY UpsertDoc(vector: [F64], content: String) =>
    existing <- V<Document>::WHERE(_::{content}::EQ(content))
    doc <- existing::UpsertV(vector, {content: content})
    RETURN doc
V::Document {
    content: String,
}

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 document
doc = client.query("UpsertDoc", {
    "vector": [0.1, 0.2, 0.3, 0.4],
    "content": "Introduction to machine learning",
})

print("Created document:", doc)

# Subsequent calls with same vector update the existing document
updated = client.query("UpsertDoc", {
    "vector": [0.1, 0.2, 0.3, 0.4],
    "content": "Updated introduction to machine learning",
})

print("Updated document:", 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 document
    let doc: serde_json::Value = client.query("UpsertDoc", &json!({
        "vector": [0.1, 0.2, 0.3, 0.4],
        "content": "Introduction to machine learning",
    })).await?;

    println!("Created document: {doc:#?}");

    // Subsequent calls update the existing document
    let updated: serde_json::Value = client.query("UpsertDoc", &json!({
        "vector": [0.1, 0.2, 0.3, 0.4],
        "content": "Updated introduction to machine learning",
    })).await?;

    println!("Updated document: {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 document
    payload := map[string]any{
        "vector":  []float32{0.1, 0.2, 0.3, 0.4},
        "content": "Introduction to machine learning",
    }

    var doc map[string]any
    if err := client.Query("UpsertDoc", helix.WithData(payload)).Scan(&doc); err != nil {
        log.Fatalf("UpsertDoc failed: %s", err)
    }

    fmt.Printf("Created document: %#v\n", doc)

    // Subsequent calls update the existing document
    payload["content"] = "Updated introduction to machine learning"

    var updated map[string]any
    if err := client.Query("UpsertDoc", helix.WithData(payload)).Scan(&updated); err != nil {
        log.Fatalf("UpsertDoc failed: %s", err)
    }

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

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

    // First call creates a new document
    const doc = await client.query("UpsertDoc", {
        vector: [0.1, 0.2, 0.3, 0.4],
        content: "Introduction to machine learning",
    });

    console.log("Created document:", doc);

    // Subsequent calls update the existing document
    const updated = await client.query("UpsertDoc", {
        vector: [0.1, 0.2, 0.3, 0.4],
        content: "Updated introduction to machine learning",
    });

    console.log("Updated document:", updated);
}

main().catch((err) => {
    console.error("UpsertDoc query failed:", err);
});
# First call creates a new document
curl -X POST \
  http://localhost:6969/UpsertDoc \
  -H 'Content-Type: application/json' \
  -d '{"vector":[0.1,0.2,0.3,0.4],"content":"Introduction to machine learning"}'

# Subsequent calls update the existing document
curl -X POST \
  http://localhost:6969/UpsertDoc \
  -H 'Content-Type: application/json' \
  -d '{"vector":[0.1,0.2,0.3,0.4],"content":"Updated introduction to machine learning"}'

Example 2: Upsert vector using the Embed function

You can use the built-in Embed function to generate embeddings from text.

⚠️ Warning

All vectors in a vector type must have the same dimensions. If you change your embedding model, the new vectors will have different dimensions and will cause an error. Ensure you use the same embedding model consistently for all vectors.

QUERY UpsertDocEmbed(text: String) =>
    existing <- V<Document>::WHERE(_::{content}::EQ(text))
    doc <- existing::UpsertV(Embed(text), {content: text})
    RETURN doc
V::Document {
    content: String,
}
OPENAI_API_KEY=your_api_key

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

from helix.client import Client

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

# Upsert with automatic embedding generation
doc = client.query("UpsertDocEmbed", {
    "text": "Introduction to machine learning",
})

print("Upserted document:", doc)
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);

    // Upsert with automatic embedding generation
    let doc: serde_json::Value = client.query("UpsertDocEmbed", &json!({
        "text": "Introduction to machine learning",
    })).await?;

    println!("Upserted document: {doc:#?}");

    Ok(())
}
package main

import (
    "fmt"
    "log"

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

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

    // Upsert with automatic embedding generation
    payload := map[string]any{
        "text": "Introduction to machine learning",
    }

    var doc map[string]any
    if err := client.Query("UpsertDocEmbed", helix.WithData(payload)).Scan(&doc); err != nil {
        log.Fatalf("UpsertDocEmbed failed: %s", err)
    }

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

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

    // Upsert with automatic embedding generation
    const doc = await client.query("UpsertDocEmbed", {
        text: "Introduction to machine learning",
    });

    console.log("Upserted document:", doc);
}

main().catch((err) => {
    console.error("UpsertDocEmbed query failed:", err);
});
curl -X POST \
  http://localhost:6969/UpsertDocEmbed \
  -H 'Content-Type: application/json' \
  -d '{"text":"Introduction to machine learning"}'

Example 3: Complex operation with nodes, edges, and vectors

QUERY ComplexUpsertOperation(
    person_name: String,
    person_age: U32,
    company_name: String,
    position: String,
    resume_content: String
) =>
    existing_person <- N<Person>::WHERE(_::{name}::EQ(person_name))
    person <- existing_person::UpsertN({name: person_name, age: person_age})
    existing_company <- N<Company>::WHERE(_::{name}::EQ(company_name))
    company <- existing_company::UpsertN({name: company_name})
    existing_edge <- E<WorksAt>
    edge <- existing_edge::UpsertE({position: position})::From(person)::To(company)
    existing_resume <- V<Resume>::WHERE(_::{content}::EQ(resume_content))
    resume <- existing_resume::UpsertV(Embed(resume_content), {content: resume_content})
    RETURN person

QUERY GetPerson(name: String) =>
    person <- N<Person>::WHERE(_::{name}::EQ(name))
    RETURN person
N::Person {
    INDEX name: String,
    age: U32,
}

N::Company {
    INDEX name: String,
}

V::Resume {
    content: String,
}

E::WorksAt {
    From: Person,
    To: Company,
    Properties: {
        position: 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 or update person, company, employment, and resume all at once
result = client.query("ComplexUpsertOperation", {
    "person_name": "Alice",
    "person_age": 30,
    "company_name": "TechCorp",
    "position": "Software Engineer",
    "resume_content": "Experienced software engineer with 5 years...",
})

print("Upserted person:", result)

# Running again will update existing records instead of creating duplicates
result = client.query("ComplexUpsertOperation", {
    "person_name": "Alice",
    "person_age": 31,  # Updated age
    "company_name": "TechCorp",
    "position": "Senior Software Engineer",  # Updated position
    "resume_content": "Experienced senior software engineer with 6 years...",
})

print("Updated 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);

    // Create or update person, company, employment, and resume all at once
    let result: serde_json::Value = client.query("ComplexUpsertOperation", &json!({
        "person_name": "Alice",
        "person_age": 30,
        "company_name": "TechCorp",
        "position": "Software Engineer",
        "resume_content": "Experienced software engineer with 5 years...",
    })).await?;

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

    // Running again will update existing records instead of creating duplicates
    let result: serde_json::Value = client.query("ComplexUpsertOperation", &json!({
        "person_name": "Alice",
        "person_age": 31,
        "company_name": "TechCorp",
        "position": "Senior Software Engineer",
        "resume_content": "Experienced senior software engineer with 6 years...",
    })).await?;

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

    Ok(())
}
package main

import (
    "fmt"
    "log"

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

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

    // Create or update person, company, employment, and resume all at once
    payload := map[string]any{
        "person_name":    "Alice",
        "person_age":     uint32(30),
        "company_name":   "TechCorp",
        "position":       "Software Engineer",
        "resume_content": "Experienced software engineer with 5 years...",
    }

    var result map[string]any
    if err := client.Query("ComplexUpsertOperation", helix.WithData(payload)).Scan(&result); err != nil {
        log.Fatalf("ComplexUpsertOperation failed: %s", err)
    }

    fmt.Printf("Upserted person: %#v\n", result)

    // Running again will update existing records instead of creating duplicates
    payload["person_age"] = uint32(31)
    payload["position"] = "Senior Software Engineer"
    payload["resume_content"] = "Experienced senior software engineer with 6 years..."

    if err := client.Query("ComplexUpsertOperation", helix.WithData(payload)).Scan(&result); err != nil {
        log.Fatalf("ComplexUpsertOperation failed: %s", err)
    }

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

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

    // Create or update person, company, employment, and resume all at once
    let result = await client.query("ComplexUpsertOperation", {
        person_name: "Alice",
        person_age: 30,
        company_name: "TechCorp",
        position: "Software Engineer",
        resume_content: "Experienced software engineer with 5 years...",
    });

    console.log("Upserted person:", result);

    // Running again will update existing records instead of creating duplicates
    result = await client.query("ComplexUpsertOperation", {
        person_name: "Alice",
        person_age: 31,
        company_name: "TechCorp",
        position: "Senior Software Engineer",
        resume_content: "Experienced senior software engineer with 6 years...",
    });

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

main().catch((err) => {
    console.error("ComplexUpsertOperation query failed:", err);
});
# Create or update person, company, employment, and resume all at once
curl -X POST \
  http://localhost:6969/ComplexUpsertOperation \
  -H 'Content-Type: application/json' \
  -d '{
    "person_name": "Alice",
    "person_age": 30,
    "company_name": "TechCorp",
    "position": "Software Engineer",
    "resume_content": "Experienced software engineer with 5 years..."
  }'

# Running again will update existing records
curl -X POST \
  http://localhost:6969/ComplexUpsertOperation \
  -H 'Content-Type: application/json' \
  -d '{
    "person_name": "Alice",
    "person_age": 31,
    "company_name": "TechCorp",
    "position": "Senior Software Engineer",
    "resume_content": "Experienced senior software engineer with 6 years..."
  }'

How Upsert differs from Add

OperationBehavior
AddVAlways creates a new vector
UpsertVOperates on traversal context: updates if vectors found, creates if empty

💡 Tip

When updating, UpsertV merges properties: it updates specified properties while preserving any existing properties that aren’t included in the upsert. The vector data itself is also updated.