Summary#
osy run puts data in. osy query gets it out.
$ osy query "Job.OrderBy(j => j.Position)"
[
{ "Title": "re-plaster the hall", "Room": "hall", "Position": 10 },
{ "Title": "paint the shed", "Room": "garden", "Position": 20 }
]
$ osy query "Job.Where(j => !j.Done).Count()"
2Signature#
osy query "<expression>" // evaluate as ANONYMOUS — what a signed-out visitor sees
osy query "<expression>" --as ada@example.com // …as one of your users — what THEY see
osy query "<expression>" --json // the whole result envelope, for a scriptDescription#
It is not a query language#
The expression is lexed, parsed, resolved and run by the same compiler your source goes through. There is no second grammar to learn and none to drift: whatever you could write inside a function body, you can write here.
That includes the refusals. A verb the language does not have comes back in the resolver's own words, with the closed set it does have:
$ osy query "Job.Nope(j => j.Title)"
✗ unknown query method 'Nope' — on 'Job' rows read from the data store the verbs are: All, Any, Average,
Count, Distinct, … OrderBy, OrderByDescending, … WhereWhat it answers with#
Whatever the expression evaluates to, rendered as JSON — because an expression has no single shape:
| you write | you get |
|---|---|
Job.OrderBy(j => j.Position) | an array of rows |
Job.Count() | a number — a scalar is a scalar, not a one-element array |
Job.Select(j => j.Title) | an array of strings |
Job.First() | one row |
A row shows the values the read actually brought back. A column your acting principal may not see is simply absent, which is the honest rendering of what happened rather than a hole where a value would be.
Reading as one of your users#
⭐ --as takes no password, and that is the point. Anonymous is the wrong default to be stuck with: on an app
with real security an anonymous read returns nothing, so the verb would be useless exactly where it matters —
and the only alternative would be reading with security off and returning everything. Neither answers the
question you actually have, which is what does this user see?
$ osy query "Job.Count()" // as a signed-out visitor
$ osy query "Job.Count()" --as ada@example.com // as Ada
$ osy query "Job.Count()" --as sam@example.com // as SamYour security { } applies in every case. If the three answers differ, that IS your rules working — this is the
cheapest way to see them do it.
Why that is safe, and where the line is#
⛔ osy query refuses to WRITE. An expression that creates, updates or deletes is rejected before it runs, and
the check is fail-closed: anything that cannot be proven read-only counts as writing.
That refusal is what makes the password-free --as sound. You already have developer authority over this app — you
can compile arbitrary code into it — so reading as one of its users grants you nothing you did not already have.
Writing as them is impersonation, which authority over an app does not confer over its people, so it stays
behind that user's own password:
$ osy query "new Job { Title = \"sneaky\" }"
✗ `query` READS — this expression writes data, so it is refused here. Anything that creates, updates or
deletes belongs in one of the app's own functions: run it with `osy run <Function>`.
$ osy run AddJob --arg title="re-plaster the hall" --as ada@example.com --password …Checking what you just wrote#
The pair is the point. osy run makes something happen; osy query says whether it did:
$ osy run AddJob --arg title="re-plaster the hall"
$ osy query "Job.Count()"
1See also#
- Running a function — the other half: making the app DO something
- Importing data — loading many rows from a file
- Querying data — the query surface itself, and what becomes SQL
- The security model — the rules
--asis showing you