Session Context
Scope one stored query to the session that executes it. Write the query once; each session that runs it sees only its own data, and no identity value passes in the request. The scoping lives in the query text, so the session cannot widen it at run time.
Session functions resolve values from the session executing the query. A Run user executing it resolves to their own id and tags. A profile owner testing the query gets the authoring fallback, and an execution without a user session, like an analysis, is rejected.
A shared dashboard is the typical use: one query behind a widget, where each Run user sees only their own devices.
A worked example
Two customers, Acme and Globex, share one dashboard. Each customer's Run users must see only their own devices' temperatures.
1. Tag the devices. Give every device a customer tag: Acme's devices get customer = acme, Globex's
get customer = globex.
2. Tag the Run users with the same key. Give each Run user a customer tag for the customer they belong
to: Acme's Run users get customer = acme, Globex's get customer = globex.
3. Write the query. Store one query that reads the customer tag from the Run user who runs it:
SELECT time, value
FROM device_data_by_tag('customer', COALESCE(session_user_tag('customer'), 'acme')) AS d
WHERE variable = 'temperature' AND time > $1
ORDER BY device
LIMIT 100
session_user_tag('customer') is the value of the customer tag on the Run user who runs the query. The
COALESCE gives it a fallback of acme for running the query as the profile owner while you build it (see
Providing a fallback while authoring).
4. Grant it. Grant the Run users execution through Access Management. They run it by id and never see or edit the SQL.
The same stored query returns different rows depending on who runs it:
- A Run user tagged
customer = acmeruns it. The server fills thecustomervalue withacme, so only Acme's devices come back. - A Run user tagged
customer = globexruns the same query. The fill isglobex, so only Globex's devices come back. - The profile owner runs it to test. The owner has no
customertag, so theCOALESCEfallbackacmeapplies and Acme's devices come back.
Access Management still decides which query a Run user may run. The session value decides which rows that run returns. They are independent: a grant lets a Run user run the query as written, and the session value inside it narrows the result to that Run user.
Session functions
These functions read from the session running the query:
| Function | Fills in |
|---|---|
session_user_id() | The id of the user running the query. Takes no arguments. |
session_user_tag('key') | That user's value for the tag key. Takes one argument, the literal tag key. |
The tag key is a literal you write; the value resolves from the session. session_user_tag('customer') is
the session's value for the customer tag. No session value is ever passed in the request body, which keeps
the contract visible in the query text.
Where they are allowed
A session function stands in for a value the query would otherwise hard-code. It is allowed in the value slot
of a tag selector (device_tag, devices_tag, entity_tag, entities_tag, and the device_data_by_tag
pairs) and in the id slot of device(...) and entity(...). The tag key itself stays a literal.
device_data_by_tag('customer', COALESCE(session_user_tag('customer'), 'acme'))
device_tag('owner', COALESCE(session_user_id(), '68b8a48a474dbd307bd84da3'))
Anywhere else (a WHERE operand, the SELECT list, LIMIT) is rejected when you save the query. Scoping by
a tag value is almost always the right choice; a raw user id used directly as a device id rarely matches
anything.
Providing a fallback while authoring
While authoring a query you run it as the profile owner, and the owner session usually has no customer tag
to fill in. Use COALESCE (the standard SQL clause) to give the session function a fallback value for
running and iterating:
COALESCE(session_user_tag('customer'), 'acme')
COALESCE(session_user_id(), '68b8a48a474dbd307bd84da3')
TagoSQL accepts COALESCE here in one shape: the session function first, then one non-empty string as the
fallback. That is enough to test with a real tag value.
The fallback applies only when the profile owner runs the query. A Run user never gets it: the value always comes from that Run user's own session, so one customer is never shown another's rows through a shared fallback. A Run user who lacks the tag gets an empty result, not the fallback.
Empty results when the session has no data
When the resolved session has access to nothing, the result is empty: zero rows, HTTP 200, billed as
normal output. Never an error, never the fallback. This covers a session whose tag value matches no devices
and a session with no tag at all. An untagged Run user sees an empty dashboard, not an error, and no resolved
value ever appears in an error message.
This is an exception the session values carry: the same misses with literal values written in the query text
keep their regular 404 errors (see Errors). The empty result reports
columns: [] and rows: []. One error still stands: a tag matching more devices
than the fan-out device cap keeps the same 400 telling you to narrow the tags or paginate, a resource limit
rather than an access question.
Reference
Argument rules. The tag key in session_user_tag is a non-empty single-quoted string; expressions,
column references, $n placeholders, and empty strings are rejected when you save. session_user_id takes
no arguments. Function names are case-insensitive, may appear together and more than once in one query,
and do not count toward the parameter cap. Full error messages are on the Errors
page.
Caching. Resolved session values join the cache key alongside the $n parameter values. Two sessions
share a cached result only when every resolved value is identical, so caching is effectively per session.
Changing a tag a session resolves does not invalidate earlier cached results; the old key stops being
produced and ages out. See Executing Queries.
The session_context field. List and info responses carry a read-only session_context flag, computed
from the query text: true when the query uses a session function. On GET /sql, request it with
fields[]=session_context. GET /sql/{id} takes no field selection and always returns the full object,
session_context included. Query editors use it to mark "this query resolves per session" without reading
the SQL. It is never accepted in a request body.
Stored query execution only. Session functions resolve only on stored query execution
(POST /sql/{id}/execute). Run any other way, a session query is rejected with
Session functions are only available on stored query execution.