examples · ask · 3 / 6
3. Return the compiled Plan tree
← Ask exampleswhat this does
Same call as before, but with "show_plan": true in the body. The response gets an extra plan field that shows the executor plan tree the compiler produced - the same shape you'd pass to /v1/query.
show_plan is a body field, not a query-string param. The engine reads it from the JSON body. A ?show_plan=true on the URL is silently ignored.
when to use it
- You're about to put a question in a hot path and need to confirm it lands on the right index.
- The same question is returning slower than you expect and you want to see what the planner picked.
- You want to lift the plan and call
/v1/querydirectly to skip the compile cost on every run.
Leave it off in production application code - the plan tree is a few KB and you don't need it on every call.
the schema
namespace = "shop"
table = "orders"
primary_key = ["id"]
[[columns]]
name = "id"
ty = "str"
required = true
[[columns]]
name = "customer_id"
ty = "str"
[[columns]]
name = "amount"
ty = "f64"
[[columns]]
name = "status"
ty = "str"
[[indexes]]
name = "by_status"
columns = ["status"] seed data
# Seed a handful of paid and pending orders so the plan has work to do.
curl -X POST "https://$OC_HOST/v1/tenants/$OC_TENANT/rows/shop.orders/_batch" \
-H "Authorization: Bearer $OC_TOKEN" \
-H "Content-Type: application/json" \
-d '[
{ "id": "o_1", "customer_id": "c_1", "amount": 19.00, "status": "paid" },
{ "id": "o_2", "customer_id": "c_2", "amount": 42.00, "status": "paid" },
{ "id": "o_3", "customer_id": "c_1", "amount": 11.50, "status": "pending" }
]' the request
POST /v1/tenants/:t/ask
curl -X POST "https://$OC_HOST/v1/tenants/$OC_TENANT/ask" \
-H "Authorization: Bearer $OC_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"nl": "count of paid orders",
"schemas": ["shop.orders"],
"show_plan": true
}'result = db.ask(
"count of paid orders",
schemas=["shop.orders"],
show_plan=True,
)
print(result["rows"])
print(result["plan"]) # the executor plan treeconst result = await db.ask(
"count of paid orders",
{ schemas: ["shop.orders"], show_plan: true }
);
console.log(result.rows);
console.log(result.plan); // the executor plan tree// Go SDK does not yet expose show_plan - call /ask via raw HTTP for the
// plan tree, or use the Python / TypeScript SDK during plan inspection.
result, err := db.Ask(ctx, "count of paid orders", "shop.orders")
if err != nil { /* handle */ }
fmt.Println(result.Rows) what you get back
{
"rows": [
{ "count": 2 }
],
"cache": "miss",
"plan": {
"Aggregate": {
"group": [],
"aggs": [ { "Count": { "alias": "count" } } ],
"child": {
"Filter": {
"predicate": { "Eq": { "path": "status", "value": "paid" } },
"child": { "IndexScan": { "schema": "shop.orders", "index": "by_status" } }
}
}
}
}
}
The plan field is the same JSON the engine's /v1/query endpoint accepts as input. Lift it, store it, replay it - the planner is deterministic for a fixed schema set.
how it works
- The compiler runs as usual. When
show_planis true, the resulting plan tree is serialised back into the response alongside the rows. - The plan here is
Aggregate(count) → Filter(status = paid) → IndexScan(by_status). The presence ofIndexScaninstead of plainScantells you the planner picked up theby_statusindex. - If you see
Scanwhere you expectedIndexScan, the index probably doesn't cover the predicate - add an index or rephrase.
common mistakes
- Putting
show_planin the URL. It's a body field. The query string is ignored. - Logging the plan tree on every request. It's a few KB and changes the response shape. Use it during development; turn it off in app code.
- Assuming the plan is stable across schema changes. Adding or dropping an index re-plans. The previous plan tree is no longer the one the engine will run - re-fetch it.