WOQL Variable Generation Guide (Javascript)

When writing WOQL queries with the JavaScript client, you need to create variables to bind values, pass data between query operations, and return results. TerminusDB provides four distinct methods for generating variables, each optimized for different coding styles and use cases.

This guide explains when and how to use vars(), vars_unique(), Vars(), and VarsUnique() in the javascript client.

Quick Overview

MethodReturnsNamingUse Case
vars()ArrayExact namesSimple queries, array destructuring
vars_unique()ArrayAuto-suffixedPrevent naming conflicts, local scopes
Vars()ObjectExact namesComplex queries, object property access
VarsUnique()ObjectAuto-suffixedComplex queries with local scopes

Understanding Variable Generation

Why Do We Need Variables?

In WOQL queries, variables act as placeholders that bind to values during query execution. Variables are used with unification, where variables get resolved and bind to values and constrain the query during the unfolding of the query resolution.

const [person, name] = WOQL.vars("Person", "Name");

WOQL.select([person, name],
  WOQL.triple(person, "rdf:type", "@schema:Person"),
  WOQL.triple(person, "name", name)
);

The variables person and name will hold the results returned by the query.

The Four Generation Methods

TerminusDB offers two access patterns (array vs. object) and two naming strategies (exact vs. unique):

Access Patterns:

  • Array-based (vars, vars_unique) - Use array destructuring
  • Object-based (Vars, VarsUnique) - Use property access with dot notation

Naming Strategies:

  • Exact names (vars, Vars) - Variables have exactly the names you specify
  • Unique names (vars_unique, VarsUnique) - Variables get auto-incremented suffixes

Object-Based Variables

Vars() - Variable Object with Exact Names

The Vars() function returns an object where each property is a variable. Access variables using dot notation. Names are exacly as specified which is important for getting consistent binding results from queries. These are the best to learn with.

Syntax:

const v = WOQL.Vars("var1", "var2", "var3");
// Access as: v.var1, v.var2, v.var3

When to Use:

  • Complex queries with many variables
  • Better code organization and readability
  • Self-documenting code
  • Top level query bindings variables (for the resulting tabular structure)
  • Avoiding long destructuring assignments

Example - Clean Query Structure:

const v = WOQL.Vars("person", "name", "email", "age", "city");

const query = WOQL.select([v.person, v.name, v.email],
  WOQL.and(
    WOQL.triple(v.person, "rdf:type", "@schema:Person"),
    WOQL.triple(v.person, "name", v.name),
    WOQL.triple(v.person, "email", v.email),
    WOQL.triple(v.person, "age", v.age),
    WOQL.triple(v.person, "city", v.city),
    WOQL.greater(v.age, 18)
  )
);

Example - Multiple Variable Groups:

// Organize variables by domain
const person = WOQL.Vars("id", "name", "email");
const address = WOQL.Vars("street", "city", "country");
const order = WOQL.Vars("orderId", "total", "date");

const query = WOQL.and(
  WOQL.triple(person.id, "name", person.name),
  WOQL.triple(person.id, "email", person.email),
  WOQL.triple(person.id, "address", address.street),
  WOQL.triple(order.orderId, "customer", person.id),
  WOQL.triple(order.orderId, "total", order.total)
);

Example - Arithmetic with Object Variables:

const v = WOQL.Vars("result1", "result2");

const query = WOQL.and(
  WOQL.eval(WOQL.times(2, 3), v.result1),
  WOQL.eval(WOQL.plus(10, 5), v.result2)
);

await client.query(query);
// Returns: [{ result1: 6, result2: 15 }]

Key Characteristics:

  • ✅ Cleaner code with dot notation
  • ✅ Better for complex queries
  • ✅ Self-documenting variable access
  • ✅ No long destructuring lines
  • ✅ Easy to pass variable groups around
  • ⚠️ Slightly more verbose initialization

VarsUnique() - Variable Object with Unique Names

The VarsUnique() function combines object-based access with automatic name uniqueness. This is important for deep nested queries where locally scoped variables are important (in combination with the select predicate). For advanced use cases.

Syntax:

const v = WOQL.VarsUnique("var1", "var2", "var3");
// Access as: v.var1, v.var2, v.var3
// Actual names: var1_N, var2_M, var3_P (with unique suffixes)

When to Use:

  • Complex queries needing local scopes
  • Building reusable query components
  • Preventing variable conflicts in large codebases
  • Nested query operations with select()

Example - Local Scoped Variables:

const v = WOQL.VarsUnique("result1", "result2");

const query = WOQL.limit(100).eval(WOQL.times(2, 3), v.result1);

await client.query(query);
// Returns: [{ result1_21: { "@type": "xsd:decimal", "@value": 6 }}]
// The suffix ensures uniqueness

Example - Nested Query Scopes:

// Outer scope
const outer = WOQL.VarsUnique("person", "name");

// Inner scope - won't conflict even if using same names
const inner = WOQL.VarsUnique("person", "detail");

const query = WOQL.and(
  WOQL.triple(outer.person, "name", outer.name),
  WOQL.select([inner.person, inner.detail],
    WOQL.triple(inner.person, "detail", inner.detail)
  )
);
// outer.person and inner.person have different actual names

Example - Reusable Query Functions:

function createPersonQuery() {
  // Each call gets local unique variables (eq can be used to bind variables together)
  const v = WOQL.VarsUnique("person", "name", "email");
  
  return WOQL.and(
    WOQL.triple(v.person, "rdf:type", "@schema:Person"),
    WOQL.triple(v.person, "name", v.name),
    WOQL.triple(v.person, "email", v.email)
  );
}

// Use multiple times without conflicts
const query1 = createPersonQuery();
const query2 = createPersonQuery();
const combined = WOQL.and(query1, query2); // No variable conflicts!

Key Characteristics:

  • ✅ Clean object-based access
  • ✅ Automatic name uniqueness
  • ✅ Perfect for reusable components
  • ✅ Excellent for nested scopes
  • ✅ Prevents conflicts in large queries
  • ✅ Combine with eq() to connect local and global scoped variables
  • ⚠️ Result keys include suffixes
  • ⚠️ Be careful with changing variable names as the object keys will change too
  • ⚠️ Less predictable variable names in bindings

Array-Based Variables

Array-based variables makes for neater WOQL, removing the need for the v. prefix with a one to one mapping between WOQL variable names and the javascript variable names.

vars() - Simple Array Variables

The vars() function creates an array of variables with exact names.

Syntax:

const [var1, var2, var3] = WOQL.vars("varName1", "varName2", "varName3");

When to Use:

  • Simple queries with few variables
  • When you want exact control over variable names
  • Quick prototypes and examples

Example - Basic Query:

const [person, name, age] = WOQL.vars("Person", "Name", "Age");

const query = WOQL.select([person, name, age],
  WOQL.triple(person, "rdf:type", "@schema:Person"),
  WOQL.triple(person, "name", name),
  WOQL.triple(person, "age", age)
);

const result = await client.query(query);
// Result bindings use exact names: { Person: ..., Name: ..., Age: ... }

Example - Mathematical Operations:

const [result] = WOQL.vars("result");

// Remember that the mathematical evaluation is performed on the server side!
const query = WOQL.eval(
  WOQL.times(2, 3),
  result
);

await client.query(query);
// Returns: [{ result: { "@type": "xsd:decimal", "@value": 6 }}]

Key Characteristics:

  • ✅ Simple and intuitive
  • ✅ Variables have exactly the names you provide
  • ✅ Perfect for straightforward queries
  • ⚠️ No protection against name collisions
  • ⚠️ Manual tracking required in complex queries

vars_unique() - Uniquely Named Array Variables

The vars_unique() function creates variables with automatically incremented suffixes (e.g., result_1, result_2).

Syntax:

const [var1, var2] = WOQL.vars_unique("varName1", "varName2");
// Creates: varName1_1, varName2_2 (numbers increment globally)

When to Use:

  • Prevent variable name collisions
  • Working with nested queries or multiple scopes
  • Building queries programmatically
  • Use with select() for local variable scoping

Example - Preventing Name Collisions:

// Without unique variables - potential collision
const [x] = WOQL.vars("x");
const [x2] = WOQL.vars("x"); // Same name! Could cause issues

// With unique variables - guaranteed unique
const [a] = WOQL.vars_unique("x");
const [b] = WOQL.vars_unique("x");
// a might be "x_1", b might be "x_2" - no collision

Example - Local Scope with select():

const [localResult] = WOQL.vars_unique("result");

const query = WOQL.select([localResult],
  WOQL.eval(WOQL.times(2, 3), localResult)
);

await client.query(query);
// Returns: [{ result_23: { "@type": "xsd:decimal", "@value": 6 }}]
// The suffix ensures this doesn't conflict with other "result" variables

Example - Resetting Counter for Testing:

// Reset counter to predictable value (useful for tests)
WOQL.vars_unique_reset_start(0);

const [v1] = WOQL.vars_unique("test"); // test_1
const [v2] = WOQL.vars_unique("test"); // test_2
const [v3] = WOQL.vars_unique("test"); // test_3

Key Characteristics:

  • ✅ Automatic name uniqueness
  • ✅ Perfect for nested scopes
  • ✅ Prevents accidental variable reuse
  • ✅ Can reset counter with vars_unique_reset_start()
  • ⚠️ Variable names less predictable
  • ⚠️ Result keys include suffixes

Comparison and Decision Guide

Array vs. Object Access

Choose Array-based (vars, vars_unique) when:

  • Writing simple queries with few variables
  • You prefer destructuring syntax
  • Variable count is small and fixed
  • You want minimal syntax overhead

Choose Object-based (Vars, VarsUnique) when:

  • Working with many variables
  • Building complex, multi-part queries
  • You want self-documenting code
  • Passing variable groups between functions
  • Organizing variables by domain/entity

Exact vs. Unique Naming

Choose Exact names (vars, Vars) when:

  • Working on simple, isolated queries
  • You need predictable result key names
  • Debugging and you want readable output
  • No risk of variable name conflicts
  • Building examples or documentation

Choose Unique names (vars_unique, VarsUnique) when:

  • Working with nested query scopes
  • Using select() for variable isolation
  • Building reusable query components
  • Programmatically generating queries
  • Large codebase with multiple developers
  • You need guaranteed name uniqueness

Practical Examples

Example 1: Simple Data Retrieval

// Using vars() - simple and direct
const [person, name] = WOQL.vars("Person", "Name");

const query = WOQL.select([person, name],
  WOQL.triple(person, "rdf:type", "@schema:Person"),
  WOQL.triple(person, "name", name)
);

Example 2: Complex Query with Many Variables

// Using Vars() - better organization
const v = WOQL.Vars(
  "person", "name", "email", "phone",
  "address", "city", "country", "postalCode"
);

const query = WOQL.and(
  WOQL.triple(v.person, "rdf:type", "@schema:Person"),
  WOQL.triple(v.person, "name", v.name),
  WOQL.triple(v.person, "email", v.email),
  WOQL.triple(v.person, "phone", v.phone),
  WOQL.triple(v.person, "address", v.address),
  WOQL.triple(v.address, "city", v.city),
  WOQL.triple(v.address, "country", v.country),
  WOQL.triple(v.address, "postalCode", v.postalCode)
);

Example 3: Nested Scopes with select()

// Using vars_unique() for isolated scopes
const [outerPerson] = WOQL.vars("person");
const [innerPerson, details] = WOQL.vars_unique("person", "details");

const query = WOQL.and(
  WOQL.triple(outerPerson, "rdf:type", "@schema:Person"),
  WOQL.select([details],
    WOQL.and(
      WOQL.eq(innerPerson, outerPerson),
      WOQL.triple(innerPerson, "details", details)
    )
  )
);
// innerPerson gets unique suffix, preventing confusion

Example 4: Reusable Query Components

// Using VarsUnique() for composable queries
function createFilterByAge(minAge) {
  const v = WOQL.VarsUnique("person", "age");
  return WOQL.and(
    WOQL.triple(v.person, "age", v.age),
    WOQL.greater(v.age, minAge)
  );
}

function createFilterByCity(city) {
  const v = WOQL.VarsUnique("person", "personCity");
  return WOQL.and(
    WOQL.triple(v.person, "city", v.personCity),
    WOQL.eq(v.personCity, city)
  );
}

// Compose without conflicts
const query = WOQL.and(
  createFilterByAge(18),
  createFilterByCity("London")
);
// Each function gets unique variable names automatically

Example 5: Mathematical Operations

// All four methods work for calculations

// vars - simple array
const [result1] = WOQL.vars("result");
WOQL.eval(WOQL.times(2, 3), result1);

// vars_unique - unique array
const [result2] = WOQL.vars_unique("result");
WOQL.eval(WOQL.plus(10, 5), result2);

// Vars - object access
const v1 = WOQL.Vars("sum", "product");
WOQL.and(
  WOQL.eval(WOQL.plus(5, 3), v1.sum),
  WOQL.eval(WOQL.times(5, 3), v1.product)
);

// VarsUnique - unique object access
const v2 = WOQL.VarsUnique("sum", "product");
WOQL.and(
  WOQL.eval(WOQL.plus(7, 2), v2.sum),
  WOQL.eval(WOQL.times(7, 2), v2.product)
);

Best Practices

1. Consistent Naming Conventions

Use descriptive variable names:

// ✅ Good - descriptive
const v = WOQL.Vars("employeeName", "employeeEmail", "departmentName");

// ❌ Bad - cryptic
const v = WOQL.Vars("x", "y", "z");
// ✅ Good - grouped by entity
const employee = WOQL.Vars("id", "name", "email");
const department = WOQL.Vars("id", "name", "budget");

// ❌ Bad - mixed together
const v = WOQL.Vars("empId", "empName", "deptId", "deptName");

3. Use Unique Variables for Nested Scopes

// ✅ Good - unique variables prevent conflicts
function subQuery() {
  const v = WOQL.VarsUnique("person", "name");
  return WOQL.triple(v.person, "name", v.name);
}

// ❌ Risky - exact names might conflict across scopes (in larger code bases)
function subQuery() {
  const v = WOQL.Vars("person", "name");
  return WOQL.triple(v.person, "name", v.name);
}

4. Reset Counter in Tests

// For predictable test results
beforeEach(() => {
  WOQL.vars_unique_reset_start(0);
});

test('query with unique variables', () => {
  const [v1] = WOQL.vars_unique("test"); // Always test_1
  expect(v1.name).toBe("test_1");
});

5. Choose Appropriate Method for Query Complexity

// Simple query and for bindings: use vars()
const [person, name] = WOQL.vars("person", "name");

// Medium complexity and for bindings: use Vars()
const v = WOQL.Vars("person", "name", "email", "age");

// Complex/reusable: prefer to use VarsUnique()
const v = WOQL.VarsUnique("person", "name", "email", "age", "address");

Common Patterns

Pattern 1: Filter and Return Pattern

const v = WOQL.Vars("employee", "name", "salary");

WOQL.select([v.employee, v.name],
  WOQL.and(
    WOQL.triple(v.employee, "name", v.name),
    WOQL.triple(v.employee, "salary", v.salary),
    WOQL.greater(v.salary, 50000)
  )
);

Pattern 2: Join Pattern

const v = WOQL.Vars("employee", "empName", "department", "deptName");

WOQL.and(
  WOQL.triple(v.employee, "name", v.empName),
  WOQL.triple(v.employee, "department", v.department),
  WOQL.triple(v.department, "name", v.deptName)
);

Pattern 3: Aggregation Pattern

const v = WOQL.Vars("department", "count");

WOQL.group_by([v.department], [v.count],
  WOQL.count(v.employee, v.count),
  WOQL.triple(v.employee, "department", v.department)
);

Pattern 4: Conditional Logic Pattern

const v = WOQL.Vars("person", "status", "age");

WOQL.or(
  WOQL.and(
    WOQL.triple(v.person, "age", v.age),
    WOQL.greater(v.age, 65),
    WOQL.eq(v.status, "senior")
  ),
  WOQL.and(
    WOQL.triple(v.person, "age", v.age),
    WOQL.less(v.age, 18),
    WOQL.eq(v.status, "minor")
  )
);

Troubleshooting

Issue: Variable Name Conflicts

Problem: Results are mixed up or unexpected

// Both use "result" - might conflict
const [result1] = WOQL.vars("result");
const [result2] = WOQL.vars("result");

Solution: Use unique variables

const [result1] = WOQL.vars_unique("result");
const [result2] = WOQL.vars_unique("result");
// result1 → "result_1", result2 → "result_2"

Issue: Cannot Access Variable Properties

Problem: v.varName is undefined

const [v] = WOQL.vars("person", "name"); // Wrong - returns array
// v is just "person", not an object

Solution: Use Vars() for object access

const v = WOQL.Vars("person", "name"); // Correct - returns object
// v.person and v.name are accessible

Problem: v.varName is undefined with vio:WOQLSyntaxError

const v = WOQL.Vars("person_renamed"); // Wrong - code is still using v.person
// v.person was used previously, and now getting "Not well formed WOQL JSON-LD"

Solution: Use care when renaming variables produced by Vars(), prefer only using in a local scope

const v = WOQL.Vars("person", "name"); // Correct - returns object
// v.person and v.name are accessible

Issue: Unpredictable Variable Names in Tests

Problem: Test results vary between runs

test('my test', () => {
  const [v] = WOQL.vars_unique("test");
  expect(v.name).toBe("test_1"); // Fails if counter was incremented before
});

Solution: Reset counter before test

test('my test', () => {
  WOQL.vars_unique_reset_start(0);
  const [v] = WOQL.vars_unique("test");
  expect(v.name).toBe("test_1"); // Always passes
});

Issue: Too Many Variables to Destructure

Problem: Destructuring line is too long

const [v1, v2, v3, v4, v5, v6, v7, v8] = WOQL.vars(...); // Hard to read

Solution: Use object-based approach

const v = WOQL.Vars("v1", "v2", "v3", "v4", "v5", "v6", "v7", "v8");
// Access as v.v1, v.v2, etc.

Migration Guide

From String Variables to vars()

// Old style - strings
WOQL.triple("v:Person", "v:name", "v:Name");

// New style - vars()
const [person, name] = WOQL.vars("Person", "Name");
WOQL.triple(person, name, name);

From vars() to Vars()

// Before - array destructuring
const [person, name, email, age, city] = WOQL.vars(...);

// After - object access
const v = WOQL.Vars("person", "name", "email", "age", "city");
// Access with v.person, v.name, etc.

From Vars() to VarsUnique()

// Before - potential conflicts
function createQuery() {
  const v = WOQL.Vars("person", "name");
  return WOQL.triple(v.person, "name", v.name);
}

// After - guaranteed unique
function createQuery() {
  const v = WOQL.VarsUnique("person", "name");
  return WOQL.triple(v.person, "name", v.name);
}

Quick Reference

// Import
const { vars, vars_unique, Vars, VarsUnique } = WOQL;

// Array-based with exact names
const [a, b] = WOQL.vars("a", "b");

// Array-based with unique names
const [x, y] = WOQL.vars_unique("x", "y"); // x_1, y_2

// Object-based with exact names
const v = WOQL.Vars("person", "name");
// Access: v.person, v.name

// Object-based with unique names
const u = WOQL.VarsUnique("person", "name");
// Access: u.person, u.name (but actual names have suffixes)

// Reset unique counter (for tests)
WOQL.vars_unique_reset_start(0);

Summary

Choose your variable generation method based on your needs:

  • vars() - Simple queries, array destructuring, exact names
  • vars_unique() - Simple queries with name safety, local scopes
  • Vars() - Complex queries, clean object access, exact names
  • VarsUnique() - Complex reusable queries, object access, guaranteed uniqueness

Start with vars() or Vars() for straightforward queries. Switch to vars_unique() or VarsUnique() when building reusable components, working with nested scopes, or preventing variable name conflicts in large applications.

The right choice makes your WOQL queries more readable, maintainable, and bug-free!