ESC
Type to search...
S
Soli Docs

Query Builder

Build complex database queries with a fluent, chainable API. The QueryBuilder lets you compose filters, sorting, pagination, aggregations, and eager loading step by step.

Query Builder Chaining

Chain methods to build complex queries. You can start a chain with .where(), .order(), or .limit():

# Start with .where()
let results = User
    .where("age >= @age", { "age": 18 })
    .where("active == @active", { "active": true })
    .order("created_at", "desc")
    .limit(10)
    .offset(20)
    .all

# Start with .order() — no filter needed
let recent = User.order("created_at", "desc").limit(5).all

# Start with .limit()
let sample = User.limit(3).all

# Get first result only
let first = User.where("email == @email", { "email": "[email protected]" }).first

# Count with conditions
let count = User.where("role == @role", { "role": "admin" }).count

QueryBuilder Methods

All chainable methods available on the QueryBuilder. Methods like .where(), .order(), .limit(), and .includes() return a new QueryBuilder, while .all, .first, .count execute the query.

Method Description
.where(filter, bind_vars) Add filter condition (ANDed)
.order(field, direction) Set sort order ("asc"/"desc")
.limit(n) Limit results to n documents
.offset(n) Skip first n documents
.all Execute, return all results
.first Execute, return first result
.count Execute, return count
.exists Set exists mode (chain with .first to execute, .to_query to inspect)
.pluck(field, ...) Set pluck mode for specified fields (chain with .all to execute)
.sum(field) Set sum aggregation (chain with .first to execute)
.avg(field) Set average aggregation (chain with .first to execute)
.min(field) Set minimum aggregation (chain with .first to execute)
.max(field) Set maximum aggregation (chain with .first to execute)
.group_by(field, func, agg_field) Set group-by aggregation (chain with .all to execute)
.includes(rel, ...) Eager load relations via subqueries
.includes(rel, filter, binds) Eager load with filter and optional "fields" key
.includes({ rel: [fields] }) Eager load with field projection
.select(field, ...) Select specific fields on the main collection
.fields(field, ...) Alias for .select()
.join(rel, filter?, binds?) Filter by existence of related records
.to_query Return the generated SDBQL string (for debugging)

Query Generation (SDBQL)

Under the hood, QueryBuilder methods generate SDBQL (SoliDB Query Language) queries. Use .to_query to inspect the generated output:

Soli Code Generated SDBQL
User.all FOR doc IN users RETURN doc
User.where("age >= @age", {"age": 18}) FOR doc IN users FILTER doc.age >= @age RETURN doc
User.order("name", "asc").all FOR doc IN users SORT doc.name ASC RETURN doc
User.order("name", "asc").limit(10).all FOR doc IN users SORT doc.name ASC LIMIT 10 RETURN doc
.limit(10).offset(20) ... LIMIT 20, 10 RETURN doc
User.count FOR doc IN users COLLECT WITH COUNT INTO count RETURN count
User.includes("posts") FOR doc IN users LET _rel_posts = (FOR rel IN posts FILTER rel.user_id == doc._key RETURN rel) RETURN MERGE(doc, {posts: _rel_posts})
.includes("posts", "published = @p", {"p": true}) ... FILTER rel.user_id == doc._key AND rel.published == @p RETURN rel ...
.includes({"posts": ["title"]}) ... RETURN {title: rel.title} ...
User.select("name", "email") FOR doc IN users RETURN {name: doc.name, email: doc.email, _key: doc._key}
User.join("posts") FOR doc IN users FILTER LENGTH(FOR rel IN posts FILTER rel.user_id == doc._key LIMIT 1 RETURN 1) > 0 RETURN doc
.exists.to_query FOR doc IN users ... LIMIT 1 RETURN true
.pluck("name").to_query FOR doc IN users ... RETURN doc.name
.pluck("name", "email").to_query FOR doc IN users ... RETURN {name: doc.name, email: doc.email}
.sum("balance").to_query FOR doc IN users ... RETURN SUM(doc.balance)
.group_by("country", "sum", "balance").to_query FOR doc IN users ... COLLECT group = doc.country AGGREGATE result = SUM(doc.balance) RETURN {group: group, result: result}

SDBQL Syntax

  • FOR doc IN collection instead of SELECT * FROM
  • FILTER expression instead of WHERE
  • SORT doc.field ASC/DESC instead of ORDER BY
  • @variable syntax for bind parameters
  • LET subqueries + MERGE for eager loading

Next Steps