OriginChain docs
examples · ask · 3 / 6

3. Return the compiled Plan tree

← Ask examples
what 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/query directly 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
  }'
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_plan is 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 of IndexScan instead of plain Scan tells you the planner picked up the by_status index.
  • If you see Scan where you expected IndexScan, the index probably doesn't cover the predicate - add an index or rephrase.
common mistakes
  • Putting show_plan in 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.