Osy#the first language built for agents
Agents firstAgentic appsWorkflowsDurable Execution — built inSecurityTestingThe editorThe UI modelOne program

Reference / Query

Query<T>

Query<T> — a query that has NOT run yet: composable, and passable to a function

Holds a query instead of its rows. A clause you write against a `Query<T>` joins the query rather than filtering rows already fetched, so a chain split across a binding — or across a function boundary — is still ONE SQL statement. Without it, splitting the chain reads every matching row and finishes the work in memory.

stable7 examples compiled by CIquerydatatypes

Summary#

A Query<T> is a query that has not run yet — and it is what a LINQ chain already is, whether or not you spell it. Writing the type down matters in one place: when the query has to cross a function boundary, where a parameter needs a type.

entity Film {
  [Required, MaxLength(200)] string Title;
  int Year;
  decimal Rating;

  security { allow create, read, update when IsAuthenticated || IsAnonymous; }
}

string BestOfYear(int year) {
  Query<Film> candidates = Film.Where(f => f.Year == year);       // the filter …
  return candidates.OrderByDescending(f => f.Rating).First().Title;  // … and the pick, in ONE query
}

Signature#

var         q = Film.Where(…);          // inferred — holds the query, does not run it
Query<Film> q = Film.Where(…);          // the same thing, written down
Film Best(Query<Film> q) { … }          // a parameter — the caller supplies the query
q.OrderBy(…).Take(…)                    // a clause JOINS the query
foreach (var f in q) { … }              // read as rows: HERE the query runs
q.ToList()                              // …and this is how you say "run it now" on purpose

Description#

C# spells it IQueryable<T>, and that works too#

If you reach for C#'s name, write it — IQueryable<Film> is Query<Film>, in every position, and there is no conversion or preference between them:

Film PickBestOf(IQueryable<Film> candidates) { return candidates.OrderBy(f => f.Rating).First(); }

⚠ Its SIBLING interfaces are a different answer, and the reason is worth knowing: IEnumerable<T>, IReadOnlyList<T>, IList<T> are refused, because Osy# spells those T[] and List<T>which are themselves C#, so the refusal leaves you writing C# and costs one round trip. IQueryable<T> has no such alternative spelling, so refusing it would push you off C# onto an Osy#-only word. That is why one is accepted and the others are taught.

A plain var is the same thing#

You do not have to write the type. var infers it, exactly as var q = db.Orders.Where(…) infers IQueryable in C#, so a chain split across a binding is still one statement:

```osy title="var and the written type are the same query" test app=query-deferred // Both are ONE statement: WHERE, ORDER BY and LIMIT 1 together. string BestInferred(int year) { var candidates = Film.Where(f => f.Year == year); return candidates.OrderByDescending(f => f.Rating).First().Title; }

string BestSpelled(int year) { Query<Film> candidates = Film.Where(f => f.Year == year); return candidates.OrderByDescending(f => f.Rating).First().Title; }


### What ends the deferral — `.ToList()`, and a terminal   {#ending}
The chain is deferred until something asks for the answer. Two things do, and they are how you say "read it here":

```osy title="`.ToList()` is `run it now`" test app=query-deferred
// `.ToList()` reads the rows HERE. The sort below runs over the list, in memory — which is what you want when
// you are going to ask the same rows several questions.
string BestThenCount(int year) {
  var rows = Film.Where(f => f.Year == year).ToList();
  var best = rows.OrderByDescending(f => f.Rating).First().Title;
  return best + " of " + rows.Count.ToString();
}

A terminalFirst, Single, Count, Any, Sum and friends — ends it too, because it has produced the answer. And a declared row type asks for the rows at the binding: Film[] rows = Film.Where(…);.

Passing a query to a function#

A Query<T> parameter is the half that has no other spelling: the caller decides which rows, the helper decides what to do with them, and it is still one statement.

string[] TopTitles(Query<Film> src, int take) {
  return src.OrderByDescending(f => f.Rating).Take(take).Select(f => f.Title);
}

string[] BestOf(int year) { return TopTitles(Film.Where(f => f.Year == year), 3); }
string[] BestEver()       { return TopTitles(Film.Where(f => f.Rating > 0m), 10); }

The helper is composed into each call rather than called, so its body must be a single return <query>; (or an => <query> expression body). A body that does more than that has no expression to compose, and says so.

Reading the rows#

Used anywhere rows are wanted — a foreach, a return, an argument — a Query<T> is the rows, and that is where it runs. It is only special at the head of a chain.

int CountOfYear(int year) {
  Query<Film> candidates = Film.Where(f => f.Year == year);
  return candidates.Count();                    // SELECT count(*) … WHERE Year = @year
}

string[] TitlesOfYear(int year) {
  Query<Film> candidates = Film.Where(f => f.Year == year);
  var titles = new List<string>();
  foreach (var f in candidates) { titles.Add(f.Title); }   // runs here
  return titles.ToArray();
}

Using one twice runs it twice#

This is the one thing to know, and it applies to the inferred var as much as to the written type. A deferred query is a description, not a result — so each use goes to the database, exactly as enumerating a C# IQueryable twice is two round trips. When you want the rows once and then several answers from them, say so with ToList:

string Report(int year) {
  var rows = Film.Where(f => f.Year == year).ToList();   // read ONCE …
  var howMany = rows.Count();                            // … then ask it twice, in memory
  var best = rows.Max(f => f.Rating);
  return howMany.ToString() + " films, best " + best.ToString();
}

What it will not hold#

The initializer has to be a query that has not run. The two ways to get that wrong are different mistakes, and each is named:

You wroteWhy it is refused
Query<Film> q = Film.Where(…).First();First runs it — what you have is the answer, not the query. Write the terminal where you use it.
Query<Film> q = rows.Where(…); over a Film[]those rows have already been read. To hold rows, declare the list type: Film[], List<Film>.
Query<Film> t = Film.Select(f => f.Title);the query yields string. The declared element is checked against what the query yields, which a projection changes — write Query<string>.

Why it is not a value you can store#

A Query<T> is resolved where it is used, at compile time — it is not an object that exists while the program runs, so it cannot be put in a field, returned from a function, or held in a list. That is a security boundary before it is an economy: a query becomes SQL, and a query that could travel as a value could travel to a function running in the browser. Composing in the compiler means nothing new crosses the wire.

To hand rows to something that outlives the expression, materialise them with ToList.

Do the write terminals compose too?#

Yes — a Query<T> may end in .Delete(), .Update(…) or .Insert(…) exactly as it ends in .Count(): the helper's chain composes into the caller's statement at compile time, so int PurgeVia(Query<Order> doomed) { return doomed.Delete(); } is still one statement, with the caller's security floor.

See also#

Related

Querying data

How you read data in Osy#. You write C# LINQ; it becomes one SQL statement. The rules that follow from that are the…

Where / Single / Count

Query an entity by writing a predicate over it. The query runs in the database — not a filter over rows you already…

ToList

Runs the query and materialises the rows as a `List<Entity>`. Until you call it, a query is a description of what you…

Skip / Take (paging)

Page a query with Skip(n) (OFFSET) and Take(m) (LIMIT). The count can be a compile-time constant OR a runtime integer —…

OrderBy / ThenBy

Sort a query by one key or several. `OrderBy`/`OrderByDescending` start the sort, `ThenBy`/`ThenByDescending` add…

LINQ over a local list

Query a local `List<T>`, `HashSet<T>` or `T[]` — of your own `class` values OR of plain scalars like `string[]` and…