Dynamic query building
A query in Besu is a value. A builder is a plain struct with a type parameter for the executor, so it can be bound to a variable, built conditionally, returned from a function and handed to another function. There is no separate dynamic mode to opt into.
Building conditionally
filter consumes the builder and returns the same type, so both branches of an if agree and
the result stays a normal query.
The projection is chosen the same way. Because both branches must have the same Select<..>
type, a branch that calls pick changes the row type and will not type-check against a branch
that does not — which is the point.
A helper generic over Executor
The executor is a type parameter, so a function can take a query, adjust it and give it back without knowing whether it will run on a pool, a transaction or a dry database.
The same function works inside a transaction, because &mut Transaction<'_, Dr> is an
Executor too:
If you only ever pass a database, the concrete signature is simpler:
Inspecting a query without consuming it
to_sql is the one operation that inspects a query without consuming it: it takes &self, so
a value can be rendered for a log and then run.
The limit
There is no $dynamic mutation handle. Builder methods take self, so "mutating" a query is
really rebinding it; you cannot keep one &mut query and change its shape through it. In
exchange the compiler checks every combination, and a query that was built stays valid.
The row type is fixed once it is chosen. Select<'a, E, M, R> carries R, and pick changes
it, so a function that sometimes returns users and sometimes returns counts cannot return one
type. A genuinely heterogeneous query — where the columns are not known at the call site —
has to be raw SQL.
Fragment checks the placeholder count at render time rather than compile time, and the row
still decodes positionally into the type you name. Once even the row shape is dynamic, you are
outside the typed builder; see Raw SQL.
What is checked
Gotchas
- A query borrows its executor.
Select<'a, E, ..>carries'a, so storing one in a struct means carrying the lifetime of the database or transaction as well. to_sqlclones the query. It is cheap for inspection, but calling it in a loop and then running the builder pays for both.filtercombines withAND, always. A conditionally builtORneeds oneor(..)expression, not twofiltercalls.- A helper that returns
Selectfreezes the table set.(UserTable,)will not accept a joined query; give the helper the exact row-set type it needs, or take a closure.
