Skip to main content

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 = acme runs it. The server fills the customer value with acme, so only Acme's devices come back.
  • A Run user tagged customer = globex runs the same query. The fill is globex, so only Globex's devices come back.
  • The profile owner runs it to test. The owner has no customer tag, so the COALESCE fallback acme applies 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:

FunctionFills 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.