Module: Hecks::QuerySpecification::Common::NullPolicy
- Defined in:
- lib/hecks/query_specification/common/null_policy.rb
Constant Summary collapse
- NULL_UNMATCHABLE =
THE COMPARATORS A NULL CANNOT SATISFY — the other half of
sql_predicatebelow. That one answers "the value COMPARED TO is null" (eq: nil-> IS NULL,ne: nil-> IS NOT NULL, a real convention both adapters already shared). This one answers the case nothing covered: the ROW'S OWN value is null and the value compared to is not.SQL says unknown.
NULL <> 'red'is NULL, not true, so the row is not returned. Ruby saysnil != "red"is true, so it is. The two adapters therefore answered the SAME query on the SAME data differently — Memory returning a row SQLite omitted — which is not a difference of opinion a caller can plan around.Resolved toward SQL, and not because SQL is the store: an absent or null field is UNKNOWN, not a value, and a comparison against unknown is unknown rather than true. Making SQL match Ruby instead would mean compiling every
ne:to(col <> $1 OR col IS NULL)— more to get right in two dialects, and a reliable way to lose an index — to make a real query engine agree with an in-memory one.none_in_stateis deliberately NOT here: it is a 9th, vendored comparator whoseheldis a reference id, and a row holding no reference is genuinely "not in that state" rather than unknown. %w[eq ne lt lte gt gte in contains].freeze
Class Method Summary collapse
-
.order(records, direction:, policy: nil, &key) ⇒ Object
STABLE on purpose : rows arrive already in identity order from Ports::Query::Ordering, and that base is what makes a tie deterministic rather than store-dependent.
-
.sql_order(expression, direction, policy) ⇒ Object
M3 — an UNDECLARED (
native) null policy used to render noNULLS ...clause at all here, leaving each dialect's own default to decide: Postgres puts nulls LAST on ASC (and FIRST on DESC), while#orderabove — this same "native" default, for Memory — puts nulls FIRST on ASC (and LAST on DESC), the SQLite convention. - .sql_predicate(expression, operation, value) ⇒ Object
- .unmatchable?(operation, held, want) ⇒ Boolean
Class Method Details
.order(records, direction:, policy: nil, &key) ⇒ Object
STABLE on purpose : rows arrive already in identity order from
Ports::Query::Ordering, and that base is what makes a tie deterministic
rather than store-dependent. A plain sort_by is not stable in Ruby, so
equal keys would shuffle and the identity tier would be lost exactly
where it is needed. Descending reverses both partitions, so a tie reads
identity-descending too — the same total order sql_order renders as
field DESC, id DESC.
14 15 16 17 18 19 20 21 22 23 24 25 26 |
# File 'lib/hecks/query_specification/common/null_policy.rb', line 14 def order(records, direction:, policy: nil, &key) null_rows, valued_rows = records.partition { |record| key.call(record).nil? } sorted = valued_rows.each_with_index.sort_by { |record, index| [key.call(record), index] }.map(&:first) if direction.to_s == "desc" sorted.reverse! null_rows.reverse! end case policy&.mode.to_s when "first" then null_rows + sorted when "last" then sorted + null_rows else direction.to_s == "desc" ? sorted + null_rows : null_rows + sorted end end |
.sql_order(expression, direction, policy) ⇒ Object
M3 — an UNDECLARED (native) null policy used to render no
NULLS ... clause at all here, leaving each dialect's own
default to decide: Postgres puts nulls LAST on ASC (and FIRST
on DESC), while #order above — this same "native" default,
for Memory — puts nulls FIRST on ASC (and LAST on DESC), the
SQLite convention. Same query, same data, different row order
depending only on which adapter ran it. Rendered explicitly
here instead, so an undeclared policy means the SAME total
order everywhere rather than "whatever this store already does"
— matching #order's own default rather than the other way
round, since that default is unconditional (Memory/Heki have no
dialect to defer to) and SQLite already agrees with it natively.
40 41 42 43 44 45 46 47 48 |
# File 'lib/hecks/query_specification/common/null_policy.rb', line 40 def sql_order(expression, direction, policy) direction = direction.to_s.downcase == "desc" ? "DESC" : "ASC" nulls = case policy&.mode.to_s when "first" then " NULLS FIRST" when "last" then " NULLS LAST" else direction == "DESC" ? " NULLS LAST" : " NULLS FIRST" end "#{expression} #{direction}#{nulls}, id #{direction}" end |
.sql_predicate(expression, operation, value) ⇒ Object
80 81 82 83 84 85 86 87 88 |
# File 'lib/hecks/query_specification/common/null_policy.rb', line 80 def sql_predicate(expression, operation, value) if value.nil? && operation.to_s == "eq" ["#{expression} IS NULL", []] elsif value.nil? && operation.to_s == "ne" ["#{expression} IS NOT NULL", []] else nil end end |
.unmatchable?(operation, held, want) ⇒ Boolean
76 77 78 |
# File 'lib/hecks/query_specification/common/null_policy.rb', line 76 def unmatchable?(operation, held, want) held.nil? && !want.nil? && NULL_UNMATCHABLE.include?(operation.to_s) end |