class Rotor::Query[T]
A query over a model's table, as a value: what Rotor::Model.where
and the others build, and a repository runs. Each method gives a new
query; none changes the one it is called on. Its blocks are not run:
they are translated to SQL when the query is built, so that a block
SQL cannot express raises Rotor::UntranslatableError there, and
to_sql gives the statement and its parameters.
class QUser < Rotor::Model
table "users" do
column :id, Int, primary: true
column :name, String
column :age, Int
end
end
min = 18
q = QUser.where { age >= min }.order { name }.limit(10)
q.to_sql # => ["SELECT \"id\", \"name\", \"age\" FROM \"users\" WHERE \"age\" >= $1 ORDER BY \"name\" LIMIT 10", [18]]
T is what a row gives: the model, or what select projects.
A query is Shareable: a constant (ACTIVE = User.where { active }),
a value a task's block captures or a channel carries. Its blocks'
translations are kept for the process (Rotor::Cache), so building a
query again where it was built before costs no translation.
/home/runner/work/rotor/rotor/src/query.qz:52
Classes and modules
Rotor::Query::ShapeWhat a query gives: its rows, their number, or whether there is one.
Class methods
def self.new(sources: Array[Rotor::Source], wheres: Array[Rotor::Sql], orders: Array[[Rotor::Sql, Bool]], projected: Bool, limit: Int?, offset: Int?, shape: Rotor::Query::Shape, loader: shareable (Array[Array[Rotor::Fetched]]) -> Array[T], preloads: Array[Rotor::Preload] = …, ops: Rotor::ModelOps[T]? = …) -> Rotor::Query[T]
(Made by Rotor::Model.query and the methods of this class.)
Methods
def table -> Rotor::TableInfo
The table (the first, in a join).
def sources -> Array[Rotor::Source]
Its tables: the first, then those joined to it.
def fetched -> Array[Rotor::Column]
The columns its rows have, in order (each table's in turn, in a
join): what to_sql selects.
def shape -> Rotor::Query::Shape
What it gives: its rows, their number, or whether there is one.
def limit_value -> Int?
Its limit, if any.
def where_key(name: Symbol, value: Object?) -> Rotor::Query[T]
The rows whose column name is value (the repository's get).
def where_in(name: Symbol, values: Array[Int | Float | String | Bool | Time | Bytes]) -> Rotor::Query[T]
The rows whose column name is one of values: "name" = ANY($1), the values one array parameter, whatever their number (a
preload's query).
def where_through(k: Rotor::ThroughKeys, value: Int | Float | String | Bool | Time | Bytes) -> Rotor::Query[T]
The rows a join model's rows pair with the owner of key value
(Rotor::HasMany#query through it): "id" IN (SELECT "tag_id" FROM "post_tags" WHERE "post_id" = $1).
def through_owners(k: Rotor::ThroughKeys, keys: Array[Int | Float | String | Bool | Time | Bytes]) -> Rotor::Query[[T, Int | Float | String | Bool | Time | Bytes | Nil]]
Its rows paired with the owner key of the join model's rows that pair
them with the owners of keys (a preload through it): INNER JOIN "post_tags" AS "p1" ON "p1"."tag_id" = "t0"."id" WHERE "p1"."post_id" = ANY($1), in the order of the target's primary key.
def order_by_key(name: Symbol?) -> Rotor::Query[T]
Ordered by the column name too (nil: unchanged): a preload's query.
def load(values: Hash[Symbol, Object?]) -> T
A row's T from the values of its table's columns (those of
fetched), for a query of one table.
def load_row(values: Array[Rotor::Fetched]) -> T
A row's T from the values of each of its tables.
def load_rows(rows: Array[Rotor::Row]) -> Array[T]
The Ts of the rows a database gave for to_sql.
def where(&cond: Quoted[shareable T.() -> Bool]) -> Rotor::Query[T]
The rows for which the block is true. The row is the block's
receiver (where { age > 18 }) or its parameter (where { |u| u.age > 18 }, which for comprehensions use); a block that names
nothing of the row (where { true }) is ambiguous between the two:
write where { |u| true }.
def where(&cond: Quoted[shareable (T) -> Bool]) -> Rotor::Query[T]
The rows for which the block is true. The row is the block's
receiver (where { age > 18 }) or its parameter (where { |u| u.age > 18 }, which for comprehensions use); a block that names
nothing of the row (where { true }) is ambiguous between the two:
write where { |u| true }.
def filter(&cond: Quoted[shareable (T) -> Bool]) -> Rotor::Query[T]
where, by the name for comprehensions call.
def order(&key: Quoted[shareable T.() -> Object]) -> Rotor::Query[T]
Ordered by the block's value, ascending, after the orders given before.
def order(&key: Quoted[shareable (T) -> Object]) -> Rotor::Query[T]
Ordered by the block's value, ascending, after the orders given before.
def desc -> Rotor::Query[T]
The last order descending.
def limit(n: Int) -> Rotor::Query[T]
At most n rows.
def offset(n: Int) -> Rotor::Query[T]
The rows after the first n.
def preload(&spec: Quoted[shareable T.() -> Object]) -> Rotor::Query[T]
Its rows with the associations the block names preloaded, as
Rotor::Repo#preload does after they are loaded (repo.all(User.query.preload { posts })): Ecto's preload:.
def preloaded(rows: Array[T], repo: Rotor::Repo) -> Array[T]
The rows repo loaded with the preloads done (preload).
def first -> Rotor::Query[T]
The first row: limit(1).
def select[R](&f: Quoted[shareable T.() -> R]) -> Rotor::Query[R]
The block's value for each row: a column (select { name }), an
expression, or a data value made of them (select { Summary.new(id, name) }). The block is evaluated in Quarz, not in SQL: the query
fetches the columns it reads (all of them when it calls another
method of the row or passes the row on), and the repository runs it
on each row, loaded with those columns only (reading another raises
Rotor::NotLoadedError). Counting is count, a query's shape, not
a projection.
def select[R](&f: Quoted[shareable (T) -> R]) -> Rotor::Query[R]
The block's value for each row: a column (select { name }), an
expression, or a data value made of them (select { Summary.new(id, name) }). The block is evaluated in Quarz, not in SQL: the query
fetches the columns it reads (all of them when it calls another
method of the row or passes the row on), and the repository runs it
on each row, loaded with those columns only (reading another raises
Rotor::NotLoadedError). Counting is count, a query's shape, not
a projection.
def map[R](&f: Quoted[shareable (T) -> R]) -> Rotor::Query[R]
select, by the name for comprehensions call.
def flat_map[R](&f: Quoted[shareable (T) -> Rotor::Query[R]]) -> Rotor::Query[R]
The rows of the queries the block gives for each row, in one query
with a JOIN: what a for comprehension over several queries calls
(Quarz's docs/01 §11). The block's query is a model's (Post.where { ... }, or its query), with where, filter, order and desc,
and a last map, select or flat_map, or an association's
(u.posts.query, joined by its key); its conditions, those on the row
(u) included, are the JOIN's, its orders come after this query's.
Its rows are made by the block, run for each distinct row of this
query (with the columns the block reads of it): building the inner
query again costs some microseconds per row, so a large result reads
better through join, which makes pairs.
class FUser < Rotor::Model
table "users" do
column :id, Int, primary: true
column :name, String
has_many FPost, name: :posts, foreign_key: :user_id
end
end
class FPost < Rotor::Model
table "posts" do
column :id, Int, primary: true
column :user_id, Int
column :title, String
column :published, Bool, default: false
end
end
q = for u in FUser.query, p in FPost.where { published } when p.user_id == u.id do [u.name, p.title] end
q.to_sql[0] # => "SELECT \"u0\".\"id\", \"u0\".\"name\", \"p1\".\"title\" FROM \"users\" AS \"u0\" INNER JOIN \"posts\" AS \"p1\" ON \"p1\".\"published\" AND \"p1\".\"user_id\" = \"u0\".\"id\""
pairs = for u in FUser.query, p in u.posts.query do [u, p] end
pairs.to_sql[0] # => "SELECT \"u0\".\"id\", \"u0\".\"name\", \"p1\".\"id\", \"p1\".\"user_id\", \"p1\".\"title\", \"p1\".\"published\" FROM \"users\" AS \"u0\" INNER JOIN \"posts\" AS \"p1\" ON \"p1\".\"user_id\" = \"u0\".\"id\""
def join[R <: Rotor::Model](&assoc: Quoted[shareable T.() -> Rotor::HasMany[R]]) -> Rotor::Query[[T, R]]
Its rows paired with those of the association the block names
(User.query.join { posts }), joined by its key: Ecto's assoc,
Rails' joins. Conditions, orders and projections of the pairs
follow, with a parameter for each (where { |u, p| p.published }).
class JUser < Rotor::Model
table "users" do
column :id, Int, primary: true
column :name, String
has_many JPost, name: :posts, foreign_key: :user_id
end
end
class JPost < Rotor::Model
table "posts" do
column :id, Int, primary: true
column :user_id, Int
column :title, String
end
end
q = JUser.query.join { posts }.where { |u, p| p.title.start_with?("Q") }.select { |u, p| "#{u.name}: #{p.title}" }
q.to_sql # => ["SELECT \"u0\".\"id\", \"u0\".\"name\", \"p1\".\"title\" FROM \"users\" AS \"u0\" INNER JOIN \"posts\" AS \"p1\" ON \"p1\".\"user_id\" = \"u0\".\"id\" WHERE \"p1\".\"title\" LIKE $1", ["Q%"]]
def join[R <: Rotor::Model](&assoc: Quoted[shareable (T) -> Rotor::HasMany[R]]) -> Rotor::Query[[T, R]]
Its rows paired with those of the association the block names
(User.query.join { posts }), joined by its key: Ecto's assoc,
Rails' joins. Conditions, orders and projections of the pairs
follow, with a parameter for each (where { |u, p| p.published }).
class JUser < Rotor::Model
table "users" do
column :id, Int, primary: true
column :name, String
has_many JPost, name: :posts, foreign_key: :user_id
end
end
class JPost < Rotor::Model
table "posts" do
column :id, Int, primary: true
column :user_id, Int
column :title, String
end
end
q = JUser.query.join { posts }.where { |u, p| p.title.start_with?("Q") }.select { |u, p| "#{u.name}: #{p.title}" }
q.to_sql # => ["SELECT \"u0\".\"id\", \"u0\".\"name\", \"p1\".\"title\" FROM \"users\" AS \"u0\" INNER JOIN \"posts\" AS \"p1\" ON \"p1\".\"user_id\" = \"u0\".\"id\" WHERE \"p1\".\"title\" LIKE $1", ["Q%"]]
def join[R <: Rotor::Model](other: Type[R], &cond: Quoted[shareable (T, R) -> Bool]) -> Rotor::Query[[T, R]]
Its rows paired with the rows of other for which the block is true
(User.query.join(Post) { |u, p| p.user_id == u.id }): an INNER JOIN
on the condition, for models without an association or with several.
def count -> Rotor::Query[Int]
The number of rows: SELECT count(*).
def exists -> Rotor::Query[Bool]
Whether there is a row: SELECT EXISTS (...).
def delete -> Rotor::Statement
The rows' deletion: DELETE ... WHERE with the query's conditions
(a query with an order, a limit, an offset or a projection cannot
say which rows).
def update(&set: Quoted[shareable T.() -> Hash[Symbol, Object]]) -> Rotor::Statement
The rows' update: the block gives a hash literal of columns and their
new values, expressions of the row as in where (update { {age: age + 1, name: "x"} }).
def update(&set: Quoted[shareable (T) -> Hash[Symbol, Object]]) -> Rotor::Statement
The rows' update: the block gives a hash literal of columns and their
new values, expressions of the row as in where (update { {age: age + 1, name: "x"} }).
def to_sql -> [String, Array[Object?]]
The SQL statement and its parameters.
def inspect -> String
A developer-readable representation, as p prints it: strings quoted,
collections with their elements' inspect, an object with its fields
(#<Point @x=1, @y=2>).
from Object
def to_s -> String
The value as a string, for puts and interpolation. For an instance of
a class, the class name in the form #<Point>; for an exception, its
message; for a data value or an enum variant, the default inspect.
It does not call an inspect the class defines.
from Object
def pieces -> Array[Rotor::Piece]
The statement's pieces.