Module: Hecks::Adapters::SqlQueryBuilder

Included in:
D1, Postgres, PostgresEra, Sqlite
Defined in:
lib/hecks/adapters/driven/sql_query_builder.rb

Overview

The one SQL query compilation, shared by the SQLite and Postgres adapters — each used to carry its own copy of this walk, near-line- identical, and the copies could only drift. Every declared operator compiles fully into SQL or the query refuses loudly; the null-policy predicate, the comma-separated in convention, and the identity ORDER BY fallback are spelled once, here.

What a dialect actually differs on is narrow, and each adapter supplies exactly those hooks:

placeholder(binds, value)     — "?" or "$N", recording the bind
contains_clause(expr, ph)     — instr() or position(), for a SCALAR field
list_contains_clause(name, member, ph) — real element containment,
                             for a `list_of` field (json_each /
                             jsonb_array_elements)
empty_in_clause               — "0" or "FALSE"
comparable_expression(e, v)   — ::numeric cast, where the dialect casts
plain_column(name)            — a real column or a jsonb path
nested_expression(name, path, member) — json_extract or state #>>
order_clause(order_by, policy)
execute_query(sql, binds)     — run it, return instances
dialect_name                  — for the refusal message

Constant Summary collapse

COMPARATORS =
{
  "eq" => "=", "ne" => "<>", "gt" => ">", "gte" => ">=", "lt" => "<", "lte" => "<="
}.freeze

Instance Method Summary collapse

Instance Method Details

#query(declared, args = {}, context: {}) ⇒ Object



36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
# File 'lib/hecks/adapters/driven/sql_query_builder.rb', line 36

def query(declared, args = {}, context: {})
  sql = +"SELECT #{select_list} FROM #{from_relation}"
  clauses = []
  binds = []
  declared.wheres.each do |clause|
    value = query_value(clause.value, args)
    expression = query_expression(clause.field, value: value)
    if (null_predicate = QuerySpecification::Common::NullPolicy.sql_predicate(expression, clause.op, value))
      clauses << null_predicate.first
      next
    end
    clauses << where_clause(clause.op.to_s, expression, value, binds, field: clause.field)
  end
  sql << " WHERE #{clauses.join(' AND ')}" unless clauses.empty?
  sql << if declared.order_by
           " ORDER BY #{order_clause(declared.order_by, declared.null_semantics)}"
         else
           " ORDER BY id"
         end
  sql << " LIMIT #{placeholder(binds, query_value(declared.limit.value, args).to_i)}" if declared.limit
  # An offset with no declared limit — SQLite refuses a bare OFFSET
  # outright (LIMIT -1 is its unbounded idiom) while Postgres accepts
  # one, so the dialect supplies its own unbounded spelling. Found by
  # the adapter-agreement gate on its first run, not by either
  # adapter's own spec: each was self-consistent, they just disagreed.
  sql << unbounded_limit if !declared.limit && declared.offset
  sql << " OFFSET #{placeholder(binds, query_value(declared.offset.value, args).to_i)}" if declared.offset
  execute_query(sql, binds)
end