Ask once. HeavenBase figures out who should answer.
1. Build a Query
Start from ws.query(Entity), then add filters, vector search, projection, ordering, pagination, and execution.
import heavenbase as hb
class Article(hb.Entity):
title = hb.field(hb.ShortText)
body = hb.field(hb.LongText)
tags = hb.field(hb.Array[hb.ShortText]).default([])
embedding = hb.field(hb.Vector[2])
ws = hb.HeavenBase("core-query", preset="debug")
ws.register(Article)
ws.upsert_many(
Article,
[
{"object_id": "a1", "name": "Agent memory", "title": "Agent memory", "body": "Agents need searchable memory.", "tags": ["agent"], "embedding": [1.0, 0.0]},
{"object_id": "a2", "name": "Backend plan", "title": "Backend plan", "body": "Backends store physical fields.", "tags": ["storage"], "embedding": [0.0, 1.0]},
],
)
frame = (
ws.query(Article)
.where(Article.body.match("agent"))
.near(Article.embedding, [1.0, 0.0], top_k=3)
.select("title", "score")
.limit(2)
.execute()
)
print(frame.rows())
execute() returns a ResultFrame, not a backend cursor.
2. Read a ResultFrame
ResultFrame keeps object_id even when you project a small column set.
rows = frame.rows()
print(rows[0]["object_id"])
print(frame.ids)
print(frame.col_names())
This is intentional for agents. They can display compact rows, then call get, set, delete, query, or explain later with the same identity.
3. Use Filters
Field references build typed filter expressions. You can combine them with &, |, and ~.
not_storage = ws.query(Article).where(Article.title != "Backend plan").execute()
tagged = ws.query(Article).where(Article.tags.array_contains("agent")).execute()
combined = ws.query(Article).where((Article.body.match("agent")) & (Article.title != "Draft")).execute()
print(len(not_storage), len(tagged), len(combined))
Use query_json when an agent or external client needs a serializable query spec.
json_frame = ws.query_json(
Article,
{
"filter": {
"body": {"$match": "agent"},
"tags": {"$array_contains": "agent"},
},
"select": ["title"],
"limit": 5,
},
).execute()
print(json_frame.rows())
4. Mongo-Style JSON Queries
where() also accepts a Mongo-style filter dictionary. Operator keys use a $ prefix, a bare value means $eq, and $and, $or, and $not combine clauses.
mongo_frame = (
ws.query(Article)
.where(
{
"$and": [
{"body": {"$match": "agent"}},
{"title": {"$in": ["Agent memory", "Backend plan"]}},
]
}
)
.execute()
)
print(mongo_frame.rows())
Supported $ operators map to the registered logical operations: $eq, $ne, $lt, $lte, $gt, $gte, $in, $match, $like, $ilike, $wildcard, $regex, $contains, $array_contains, $exists, and $all. On Array fields, $contains normalizes to array_contains automatically. The same filter shape works as the filter key of a query_json spec.
5. Inspect an Explain Plan
Call explain() before execution when you need routing diagnostics.
plan = ws.query(Article).where(Article.body.match("agent")).explain()
print(plan["steps"][0]["backend"])
print(plan["steps"][0]["handler_mode"])
The plan reports storage placement, selected backend, strategy, handler kind, handler mode, and unsupported reasons for fallback paths.
6. Mutate Rows Outside Query
Query reads rows. Use CRUD methods for writes, updates, and deletes.
object_id = ws.upsert(
Article,
{
"name": "Query note",
"title": "Query note",
"body": "Keep the returned object_id.",
"tags": ["docs"],
"embedding": [1.0, 0.0],
},
)
ws.set(object_id, {"title": "Updated query note"}, entity=Article)
print(ws.get(object_id, entity=Article)["title"])
print(ws.delete((Article, object_id)))
Pass entity=... when an object_id could exist under more than one entity type.
Further Exploration