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 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245
|
# frozen_string_literal: true
class Riddle::Query::Select
def initialize
@values = []
@indices = []
@matching = nil
@wheres = {}
@where_alls = {}
@where_nots = {}
@where_not_alls = {}
@group_by = nil
@group_best = nil
@having = []
@order_by = nil
@order_within_group_by = nil
@offset = nil
@limit = nil
@options = {}
end
def values(*values)
@values += values
self
end
def prepend_values(*values)
@values.insert 0, *values
self
end
def from(*indices)
@indices += indices
self
end
def matching(match)
@matching = match
self
end
def where(filters = {})
@wheres.merge!(filters)
self
end
def where_all(filters = {})
@where_alls.merge!(filters)
self
end
def where_not(filters = {})
@where_nots.merge!(filters)
self
end
def where_not_all(filters = {})
@where_not_alls.merge!(filters)
self
end
def group_by(attribute)
@group_by = attribute
self
end
def group_best(count)
@group_best = count
self
end
def having(*conditions)
@having += conditions
self
end
def order_by(order)
@order_by = order
self
end
def order_within_group_by(order)
@order_within_group_by = order
self
end
def limit(limit)
@limit = limit
self
end
def offset(offset)
@offset = offset
self
end
def with_options(options = {})
@options.merge! options
self
end
def to_sql
sql = StringIO.new String.new(""), "w"
sql << "SELECT #{ extended_values } FROM #{ @indices.join(', ') }"
sql << " WHERE #{ combined_wheres }" if wheres?
sql << " #{group_prefix} #{escape_columns(@group_by)}" if !@group_by.nil?
unless @order_within_group_by.nil?
sql << " WITHIN GROUP ORDER BY #{escape_columns(@order_within_group_by)}"
end
sql << " HAVING #{@having.join(' AND ')}" unless @having.empty?
sql << " ORDER BY #{escape_columns(@order_by)}" if !@order_by.nil?
sql << " #{limit_clause}" unless @limit.nil? && @offset.nil?
sql << " #{options_clause}" unless @options.empty?
sql.string
end
private
def extended_values
@values.empty? ? '*' : @values.join(', ')
end
def group_prefix
['GROUP', @group_best, 'BY'].compact.join(' ')
end
def wheres?
!(@wheres.empty? && @where_alls.empty? && @where_nots.empty? && @where_not_alls.empty? && @matching.nil?)
end
def combined_wheres
wheres = wheres_to_s
if @matching.nil?
wheres
elsif wheres.empty?
"MATCH(#{Riddle::Query.quote @matching})"
else
"MATCH(#{Riddle::Query.quote @matching}) AND #{wheres}"
end
end
def wheres_to_s
(
@wheres.keys.collect { |key|
filter_comparison_and_value key, @wheres[key]
} +
@where_alls.collect { |key, values|
values.collect { |value|
filter_comparison_and_value key, value
}
} +
@where_nots.keys.collect { |key|
exclusive_filter_comparison_and_value key, @where_nots[key]
} +
@where_not_alls.collect { |key, values|
'(' + values.collect { |value|
exclusive_filter_comparison_and_value key, value
}.join(' OR ') + ')'
}
).flatten.compact.join(' AND ')
end
def filter_comparison_and_value(attribute, value)
case value
when Array
if !value.flatten.empty?
"#{escape_column(attribute)} IN (#{value.collect { |val| filter_value(val) }.join(', ')})"
end
when Range
"#{escape_column(attribute)} BETWEEN #{filter_value(value.first)} AND #{filter_value(value.last)}"
else
"#{escape_column(attribute)} = #{filter_value(value)}"
end
end
def exclusive_filter_comparison_and_value(attribute, value)
case value
when Array
if !value.flatten.empty?
"#{escape_column(attribute)} NOT IN (#{value.collect { |val| filter_value(val) }.join(', ')})"
end
when Range
"#{escape_column(attribute)} < #{filter_value(value.first)} OR #{attribute} > #{filter_value(value.last)}"
else
"#{escape_column(attribute)} <> #{filter_value(value)}"
end
end
def filter_value(value)
case value
when TrueClass
1
when FalseClass
0
when Time
value.to_i
when Date
Time.utc(value.year, value.month, value.day).to_i
when String
"'#{value.gsub("'", "\\'")}'"
else
value
end
end
def limit_clause
if @offset.nil?
"LIMIT #{@limit}"
else
"LIMIT #{@offset}, #{@limit || 20}"
end
end
def options_clause
'OPTION ' + @options.keys.collect { |key|
"#{key}=#{option_value @options[key]}"
}.join(', ')
end
def option_value(value)
case value
when Hash
'(' + value.collect { |key, value| "#{key}=#{value}" }.join(', ') + ')'
else
value
end
end
def escape_column(column)
if column.to_s[/\A[`@]/] || column.to_s[/\A\w+\(/] || column.to_s[/\A\w+[.\[]/]
column
else
column_name, *extra = column.to_s.split(' ')
extra.unshift("`#{column_name}`").compact.join(' ')
end
end
def escape_columns(columns)
columns.to_s.split(/,\s*/).collect { |column|
escape_column(column)
}.join(', ')
end
end
|