Module: Hecks::Ports::Query::InMemory

Defined in:
lib/hecks/ports/query/in_memory.rb

Constant Summary collapse

FieldPath =
QuerySpecification::FieldPath
Comparison =
QuerySpecification::Common::Comparison

Class Method Summary collapse

Class Method Details

.comparable(value) ⇒ Object



56
# File 'lib/hecks/ports/query/in_memory.rb', line 56

def comparable(value) = Comparison.comparable(value)

.execute(records, declared, args = {}, registry: nil) ⇒ Object



15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
# File 'lib/hecks/ports/query/in_memory.rb', line 15

def execute(records, declared, args = {}, registry: nil)
  matched = records.select do |record|
    declared.wheres.all? { |clause|
      holds?(clause, comparable(FieldPath.dig(record, clause.field)), args, registry: registry)
    }
  end
  field   = declared.order_by&.field
  matched = Ordering.apply(matched, declared.order_by, declared.null_semantics,
                           identity: ->(record) { record.id.to_s }) { |record| comparable(FieldPath.dig(record, field)) }
  # OFFSET FIRST, THEN LIMIT — the order SQL means by `LIMIT n
  # OFFSET m`, which is what `SqlQueryBuilder` emits and therefore
  # what every SQL-backed aggregate already answers. Written the
  # other way round here, and the two engines disagreed on the
  # same declaration: `limit 2, offset 1` over three rows is rows
  # two and three in Postgres and Sqlite, and was row two alone in
  # memory. Silently — a short page reads exactly like a page that
  # ran out of rows.
  #
  # It gets worse the further you page, which is the case nobody
  # writing the first page ever sees: at `limit 10, offset 10`,
  # taking ten and then dropping ten leaves NOTHING, so page two
  # of a memory-backed query came back empty however many rows
  # were really there.
  matched = matched.drop(resolve(declared.offset.value, args).to_i) if declared.offset
  matched = matched.first(resolve(declared.limit.value, args).to_i) if declared.limit
  matched
end

.holds?(clause, held, args, registry: nil) ⇒ Boolean

The comparator table itself lives in QuerySpecification::Common::Comparison — this module and Runtime::QueryInterpreter used to carry a copy each, and the two drifted (see that file's own comment for what it cost). What stays here is how a value is REACHED for this path: a registry arrives as an argument rather than as instance state, and the field is dug through FieldPath before it arrives.

Returns:

  • (Boolean)


50
51
52
# File 'lib/hecks/ports/query/in_memory.rb', line 50

def holds?(clause, held, args, registry: nil)
  Comparison.holds?(clause.op, held, comparable(resolve(clause.value, args)), registry: registry)
end

.resolve(value, args) ⇒ Object



54
# File 'lib/hecks/ports/query/in_memory.rb', line 54

def resolve(value, args) = value.is_a?(Symbol) ? args[value] : value