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

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

/home/runner/work/rotor/rotor/src/query.qz:75

Methods

def table -> Rotor::TableInfo

The table (the first, in a join).

/home/runner/work/rotor/rotor/src/query.qz:79

def sources -> Array[Rotor::Source]

Its tables: the first, then those joined to it.

/home/runner/work/rotor/rotor/src/query.qz:82

def fetched -> Array[Rotor::Column]

The columns its rows have, in order (each table's in turn, in a join): what to_sql selects.

/home/runner/work/rotor/rotor/src/query.qz:86

def shape -> Rotor::Query::Shape

What it gives: its rows, their number, or whether there is one.

/home/runner/work/rotor/rotor/src/query.qz:89

def limit_value -> Int?

Its limit, if any.

/home/runner/work/rotor/rotor/src/query.qz:92

def where_key(name: Symbol, value: Object?) -> Rotor::Query[T]

The rows whose column name is value (the repository's get).

/home/runner/work/rotor/rotor/src/query.qz:95

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

/home/runner/work/rotor/rotor/src/query.qz:111

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

/home/runner/work/rotor/rotor/src/query.qz:123

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.

/home/runner/work/rotor/rotor/src/query.qz:136

def order_by_key(name: Symbol?) -> Rotor::Query[T]

Ordered by the column name too (nil: unchanged): a preload's query.

/home/runner/work/rotor/rotor/src/query.qz:157

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.

/home/runner/work/rotor/rotor/src/query.qz:165

def load_row(values: Array[Rotor::Fetched]) -> T

A row's T from the values of each of its tables.

/home/runner/work/rotor/rotor/src/query.qz:171

def load_rows(rows: Array[Rotor::Row]) -> Array[T]

The Ts of the rows a database gave for to_sql.

/home/runner/work/rotor/rotor/src/query.qz:174

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

/home/runner/work/rotor/rotor/src/query.qz:201

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

/home/runner/work/rotor/rotor/src/query.qz:204

def filter(&cond: Quoted[shareable (T) -> Bool]) -> Rotor::Query[T]

where, by the name for comprehensions call.

/home/runner/work/rotor/rotor/src/query.qz:207

def order(&key: Quoted[shareable T.() -> Object]) -> Rotor::Query[T]

Ordered by the block's value, ascending, after the orders given before.

/home/runner/work/rotor/rotor/src/query.qz:210

def order(&key: Quoted[shareable (T) -> Object]) -> Rotor::Query[T]

Ordered by the block's value, ascending, after the orders given before.

/home/runner/work/rotor/rotor/src/query.qz:213

def desc -> Rotor::Query[T]

The last order descending.

/home/runner/work/rotor/rotor/src/query.qz:216

def limit(n: Int) -> Rotor::Query[T]

At most n rows.

/home/runner/work/rotor/rotor/src/query.qz:222

def offset(n: Int) -> Rotor::Query[T]

The rows after the first n.

/home/runner/work/rotor/rotor/src/query.qz:229

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

/home/runner/work/rotor/rotor/src/query.qz:238

def preloaded(rows: Array[T], repo: Rotor::Repo) -> Array[T]

The rows repo loaded with the preloads done (preload).

/home/runner/work/rotor/rotor/src/query.qz:248

def first -> Rotor::Query[T]

The first row: limit(1).

/home/runner/work/rotor/rotor/src/query.qz:256

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.

/home/runner/work/rotor/rotor/src/query.qz:266

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.

/home/runner/work/rotor/rotor/src/query.qz:272

def map[R](&f: Quoted[shareable (T) -> R]) -> Rotor::Query[R]

select, by the name for comprehensions call.

/home/runner/work/rotor/rotor/src/query.qz:278

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\""

/home/runner/work/rotor/rotor/src/query.qz:318

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%"]]

/home/runner/work/rotor/rotor/src/query.qz:369

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%"]]

/home/runner/work/rotor/rotor/src/query.qz:375

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.

/home/runner/work/rotor/rotor/src/query.qz:383

def count -> Rotor::Query[Int]

The number of rows: SELECT count(*).

/home/runner/work/rotor/rotor/src/query.qz:402

def exists -> Rotor::Query[Bool]

Whether there is a row: SELECT EXISTS (...).

/home/runner/work/rotor/rotor/src/query.qz:408

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

/home/runner/work/rotor/rotor/src/query.qz:416

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"} }).

/home/runner/work/rotor/rotor/src/query.qz:424

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"} }).

/home/runner/work/rotor/rotor/src/query.qz:427

def to_sql -> [String, Array[Object?]]

The SQL statement and its parameters.

/home/runner/work/rotor/rotor/src/query.qz:430

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

/home/runner/work/rotor/rotor/src/query.qz:432

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

/home/runner/work/rotor/rotor/src/query.qz:434

def pieces -> Array[Rotor::Piece]

The statement's pieces.

/home/runner/work/rotor/rotor/src/query.qz:437