Module: Hecks::Translation::RuleCompiler
- Defined in:
- lib/hecks/translation/rule_compiler.rb
Overview
The closed, PURE half of Postgres's own SQL compiler (adapters/driven/postgres_era/lineage/head_compiler.rb) — the part that turns one TranslationAggregate's declared rules into a jsonb-transforming SQL expression. No database connection, no watermark, no era chain, no catalog lookup: those stay exactly where they were, in head_compiler.rb's own per-mint assembly, which calls into this module instead of defining these methods itself.
Extracted here — not left as private methods on
Adapters::PostgresEra::Lineage — so a SECOND, adapter-agnostic
caller (Exporter.translation_aggregate's build-time SQL export,
feeding rust/host's own future boot-time mint) can call the exact
SAME code Ruby's own mint path runs, not a hand-ported duplicate
that could silently drift the way Exporter.translation_hash
drifted from hecks_eras/hecks_approvals' real schema before
this file existed (rekeys/backfills were missing for years).
Class Method Summary collapse
-
.compile_compute(expression, compute) ⇒ Object
A compute is the one rule whose SQL is its only implementation — evaluated exclusively inside the compiled head, never in-process.
-
.compile_id_expression(declared) ⇒ Object
THE REKEY'S OWN SQL — reading
statedirectly, not the progressively-builtexpressionchaincompile_computereads from. -
.compile_rules(declared) ⇒ Object
One edge's rules over one jsonb state, compiled as a nested expression tree — hecks_tr_* helpers composed innermost-first in the reference transform's phase order (renames, moves, converts, drops), computes last.
-
.id_case(guard, declared) ⇒ Object
THE ONLY TWO PLACES
aggregate_idNEEDS TO CHANGE — guarded so the generated SQL for the overwhelming common case (no rekey declared) stays the bareaggregate_idpassthrough it always was — this CASE only appears in an edge that actually declares one. - .path_literal(path) ⇒ Object
-
.quote(name) ⇒ Object
PG::Connection.quote_identneeds thepggem LOADED, not connected — required here, lazily, the same "a domain that never wires PostgresEra should never need the gem" reasoningPostgresEra.connect_for's ownrequire "pg"already holds itself to, so a build tool that exports translations for a non-Postgres-bound domain (there are none today, but nothing here should assume there never will be) doesn't gain a hard dependency onpgjust by loading this file. -
.rekeyed?(declared) ⇒ Boolean
Whether THIS edge's declared rules for this aggregate include a rekey — checked directly off the raw IR object, the same way every other rule kind is already read in
compile_rules(declared.computes,declared.moves, ...), not through thePorts::Persistence::Lineagewrapper the app-level consumers (coverage_check.rb, minter.rb, layer_two.rb) go through — this module builds SQL straight off the IR either way. - .text_literal(text) ⇒ Object
Class Method Details
.compile_compute(expression, compute) ⇒ Object
A compute is the one rule whose SQL is its only implementation — evaluated exclusively inside the compiled head, never in-process. The old field is exposed under its own name (as text, exactly as the author's expression expects to cast it).
88 89 90 91 92 93 94 95 96 97 |
# File 'lib/hecks/translation/rule_compiler.rb', line 88 def compile_compute(expression, compute) from = compute.from.to_s to = compute.to.to_s "(SELECT CASE WHEN __s ? #{text_literal(from)} THEN " \ "hecks_tr_insert(__s - #{text_literal(from)}, #{path_literal(to)}, to_jsonb((#{compute.sql})), " \ "#{text_literal("compute #{from} to: #{to}")}) " \ "ELSE __s END " \ "FROM (SELECT (#{expression}) AS __s) __outer, " \ "LATERAL (SELECT (__s ->> #{text_literal(from)}) AS #{quote(from)}) __fields)" end |
.compile_id_expression(declared) ⇒ Object
THE REKEY'S OWN SQL — reading state directly, not the
progressively-built expression chain compile_compute reads
from. A rekey doesn't consume or move any field the way a move
or compute does, so there is no same-edge rename/move ordering
it needs to see first — it reads the record's stored fields
exactly as they already are, the same __s convention
compile_compute exposes.
79 80 81 82 |
# File 'lib/hecks/translation/rule_compiler.rb', line 79 def compile_id_expression(declared) rekey = declared.rekeys.first "(SELECT (#{rekey.sql}) FROM (SELECT (state) AS __s) __outer)" end |
.compile_rules(declared) ⇒ Object
One edge's rules over one jsonb state, compiled as a nested
expression tree — hecks_tr_* helpers composed innermost-first
in the reference transform's phase order (renames, moves,
converts, drops), computes last. retype compiles to nothing:
stored state never carries a type name.
30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 |
# File 'lib/hecks/translation/rule_compiler.rb', line 30 def compile_rules(declared) expression = "state" declared.renames.each do |old_name, new_name| expression = "hecks_tr_rename(#{expression}, #{text_literal(old_name)}, #{text_literal(new_name)})" end declared.moves.each do |move| expression = "hecks_tr_move(#{expression}, #{path_literal(move.from)}, #{path_literal(move.to)}, " \ "#{text_literal("move #{move.from} to: #{move.to}")})" end declared.converts.each do |convert| pairs = JSON.generate(convert.values.map { |key, value| [key, value] }) expression = "hecks_tr_convert(#{expression}, #{path_literal(convert.from)}, #{path_literal(convert.to)}, " \ "#{text_literal(pairs)}::jsonb, #{text_literal(convert.from)}, " \ "#{text_literal("convert #{convert.from} to: #{convert.to}")})" end declared.drops.each do |name| expression = "hecks_tr_drop(#{expression}, #{path_literal(name)})" end declared.computes.each do |compute| expression = compile_compute(expression, compute) end expression end |
.id_case(guard, declared) ⇒ Object
THE ONLY TWO PLACES aggregate_id NEEDS TO CHANGE — guarded so
the generated SQL for the overwhelming common case (no rekey
declared) stays the bare aggregate_id passthrough it always
was — this CASE only appears in an edge that actually declares
one.
68 69 70 |
# File 'lib/hecks/translation/rule_compiler.rb', line 68 def id_case(guard, declared) "CASE WHEN #{guard} THEN #{compile_id_expression(declared)} ELSE aggregate_id END AS aggregate_id" end |
.path_literal(path) ⇒ Object
114 115 116 117 |
# File 'lib/hecks/translation/rule_compiler.rb', line 114 def path_literal(path) segments = path.to_s.split(".").map { |segment| text_literal(segment) } "ARRAY[#{segments.join(', ')}]::text[]" end |
.quote(name) ⇒ Object
PG::Connection.quote_ident needs the pg gem LOADED, not
connected — required here, lazily, the same "a domain that
never wires PostgresEra should never need the gem" reasoning
PostgresEra.connect_for's own require "pg" already holds
itself to, so a build tool that exports translations for a
non-Postgres-bound domain (there are none today, but nothing
here should assume there never will be) doesn't gain a hard
dependency on pg just by loading this file.
107 108 109 110 |
# File 'lib/hecks/translation/rule_compiler.rb', line 107 def quote(name) require "pg" PG::Connection.quote_ident(name.to_s) end |
.rekeyed?(declared) ⇒ Boolean
Whether THIS edge's declared rules for this aggregate include a
rekey — checked directly off the raw IR object, the same way
every other rule kind is already read in compile_rules
(declared.computes, declared.moves, ...), not through the
Ports::Persistence::Lineage wrapper the app-level consumers
(coverage_check.rb, minter.rb, layer_two.rb) go through — this
module builds SQL straight off the IR either way.
61 |
# File 'lib/hecks/translation/rule_compiler.rb', line 61 def rekeyed?(declared) = declared && !declared.rekeys.empty? |
.text_literal(text) ⇒ Object
112 |
# File 'lib/hecks/translation/rule_compiler.rb', line 112 def text_literal(text) = "'#{text.to_s.gsub("'", "''")}'" |