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 |