1 2 3 4 5 6 7 8 9 10 11 12 13 14 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 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195
|
# frozen-string-literal: true
#
# The pg_range_ops extension adds support to Sequel's DSL to make
# it easier to call PostgreSQL range and multirange functions and operators.
#
# To load the extension:
#
# Sequel.extension :pg_range_ops
#
# The most common usage is passing an expression to Sequel.pg_range_op:
#
# r = Sequel.pg_range_op(:range)
#
# If you have also loaded the pg_range or pg_multirange extensions, you can use
# Sequel.pg_range or Sequel.pg_multirange as well:
#
# r = Sequel.pg_range(:range)
# r = Sequel.pg_multirange(:range)
#
# Also, on most Sequel expression objects, you can call the pg_range
# method:
#
# r = Sequel[:range].pg_range
#
# If you have loaded the {core_extensions extension}[rdoc-ref:doc/core_extensions.rdoc],
# or you have loaded the core_refinements extension
# and have activated refinements for the file, you can also use Symbol#pg_range:
#
# r = :range.pg_range
#
# This creates a Sequel::Postgres::RangeOp object that can be used
# for easier querying:
#
# r.contains(:other) # range @> other
# r.contained_by(:other) # range <@ other
# r.overlaps(:other) # range && other
# r.left_of(:other) # range << other
# r.right_of(:other) # range >> other
# r.starts_after(:other) # range &> other
# r.ends_before(:other) # range &< other
# r.adjacent_to(:other) # range -|- other
#
# r.lower # lower(range)
# r.upper # upper(range)
# r.isempty # isempty(range)
# r.lower_inc # lower_inc(range)
# r.upper_inc # upper_inc(range)
# r.lower_inf # lower_inf(range)
# r.upper_inf # upper_inf(range)
#
# All of the above methods work for both ranges and multiranges, as long
# as PostgreSQL supports the operation. The following methods are also
# supported:
#
# r.range_merge # range_merge(range)
# r.unnest # unnest(range)
# r.multirange # multirange(range)
#
# +range_merge+ and +unnest+ expect the receiver to represent a multirange
# value, while +multi_range+ expects the receiver to represent a range value.
#
# See the PostgreSQL range and multirange function and operator documentation for more
# details on what these functions and operators do.
#
# If you are also using the pg_range or pg_multirange extension, you should
# load them before loading this extension. Doing so will allow you to use
# PGRange#op and PGMultiRange#op to get a RangeOp, allowing you to perform
# range operations on range literals.
#
# Related module: Sequel::Postgres::RangeOp
#
module Sequel
module Postgres
# The RangeOp class is a simple container for a single object that
# defines methods that yield Sequel expression objects representing
# PostgreSQL range operators and functions.
#
# Most methods in this class are defined via metaprogramming, see
# the pg_range_ops extension documentation for details on the API.
class RangeOp < Sequel::SQL::Wrapper
OPERATORS = {
:contains => ["(".freeze, " @> ".freeze, ")".freeze].freeze,
:contained_by => ["(".freeze, " <@ ".freeze, ")".freeze].freeze,
:left_of => ["(".freeze, " << ".freeze, ")".freeze].freeze,
:right_of => ["(".freeze, " >> ".freeze, ")".freeze].freeze,
:ends_before => ["(".freeze, " &< ".freeze, ")".freeze].freeze,
:starts_after => ["(".freeze, " &> ".freeze, ")".freeze].freeze,
:adjacent_to => ["(".freeze, " -|- ".freeze, ")".freeze].freeze,
:overlaps => ["(".freeze, " && ".freeze, ")".freeze].freeze,
}.freeze
%w'lower upper isempty lower_inc upper_inc lower_inf upper_inf unnest'.each do |f|
class_eval("def #{f}; function(:#{f}) end", __FILE__, __LINE__)
end
%w'range_merge multirange'.each do |f|
class_eval("def #{f}; RangeOp.new(function(:#{f})) end", __FILE__, __LINE__)
end
OPERATORS.each_key do |f|
class_eval("def #{f}(v); operator(:#{f}, v) end", __FILE__, __LINE__)
end
# These operators are already supported by the wrapper, but for ranges they
# return ranges, so wrap the results in another RangeOp.
%w'+ * -'.each do |f|
class_eval("def #{f}(v); RangeOp.new(super) end", __FILE__, __LINE__)
end
# Return the receiver.
def pg_range
self
end
private
# Create a boolen expression for the given type and argument.
def operator(type, other)
Sequel::SQL::BooleanExpression.new(:NOOP, Sequel::SQL::PlaceholderLiteralString.new(OPERATORS[type], [value, other]))
end
# Return a function called with the receiver.
def function(name)
Sequel::SQL::Function.new(name, self)
end
end
module RangeOpMethods
# Wrap the receiver in an RangeOp so you can easily use the PostgreSQL
# range functions and operators with it.
def pg_range
RangeOp.new(self)
end
end
# :nocov:
if defined?(PGRange)
# :nocov:
class PGRange
# Wrap the PGRange instance in an RangeOp, allowing you to easily use
# the PostgreSQL range functions and operators with literal ranges.
def op
RangeOp.new(self)
end
end
end
# :nocov:
if defined?(PGMultiRange)
# :nocov:
class PGMultiRange
# Wrap the PGRange instance in an RangeOp, allowing you to easily use
# the PostgreSQL range functions and operators with literal ranges.
def op
RangeOp.new(self)
end
end
end
end
module SQL::Builders
# Return the expression wrapped in the Postgres::RangeOp.
def pg_range_op(v)
case v
when Postgres::RangeOp
v
else
Postgres::RangeOp.new(v)
end
end
end
class SQL::GenericExpression
include Sequel::Postgres::RangeOpMethods
end
class LiteralString
include Sequel::Postgres::RangeOpMethods
end
end
# :nocov:
if Sequel.core_extensions?
class Symbol
include Sequel::Postgres::RangeOpMethods
end
end
if defined?(Sequel::CoreRefinements)
module Sequel::CoreRefinements
refine Symbol do
send INCLUDE_METH, Sequel::Postgres::RangeOpMethods
end
end
end
# :nocov:
|