discourse/lib/sql_builder.rb

127 lines
3.1 KiB
Ruby

class SqlBuilder
def initialize(template, klass = nil)
@args = {}
@sql = template
@sections = {}
@klass = klass
end
[:set, :where2, :where, :order_by, :limit, :left_join, :join, :offset, :select].each do |k|
define_method k do |data, args = {}|
@args.merge!(args)
@sections[k] ||= []
@sections[k] << data
self
end
end
def secure_category(secure_category_ids, category_alias = 'c')
if secure_category_ids.present?
where("NOT COALESCE(" << category_alias << ".read_restricted, false) OR " << category_alias << ".id IN (:secure_category_ids)", secure_category_ids: secure_category_ids)
else
where("NOT COALESCE(" << category_alias << ".read_restricted, false)")
end
self
end
def to_sql
sql = @sql.dup
@sections.each do |k, v|
joined = nil
case k
when :select
joined = "SELECT " << v.join(" , ")
when :where, :where2
joined = "WHERE " << v.map { |c| "(" << c << ")" }.join(" AND ")
when :join
joined = v.map { |item| "JOIN " << item }.join("\n")
when :left_join
joined = v.map { |item| "LEFT JOIN " << item }.join("\n")
when :limit
joined = "LIMIT " << v.last.to_s
when :offset
joined = "OFFSET " << v.last.to_s
when :order_by
joined = "ORDER BY " << v.join(" , ")
when :set
joined = "SET " << v.join(" , ")
end
sql.sub!("/*#{k}*/", joined)
end
sql
end
def exec(args = {})
@args.merge!(args)
sql = to_sql
if @klass
@klass.find_by_sql(ActiveRecord::Base.send(:sanitize_sql_array, [sql, @args]))
else
if @args == {}
ActiveRecord::Base.exec_sql(sql)
else
ActiveRecord::Base.exec_sql(sql, @args)
end
end
end
def self.map_exec(klass, sql, args = {})
self.new(sql).map_exec(klass, args)
end
class RailsDateTimeDecoder < PG::SimpleDecoder
def decode(string, tuple = nil, field = nil)
if Rails.version >= "4.2.0"
@caster ||= ActiveRecord::Type::DateTime.new
@caster.cast(string)
else
ActiveRecord::ConnectionAdapters::Column.string_to_time string
end
end
end
class ActiveRecordTypeMap < PG::BasicTypeMapForResults
def initialize(connection)
super(connection)
rm_coder 0, 1114
add_coder RailsDateTimeDecoder.new(name: "timestamp", oid: 1114, format: 0)
# we don't need deprecations
self.default_type_map = PG::TypeMapInRuby.new
end
end
def self.pg_type_map
conn = ActiveRecord::Base.connection.raw_connection
@typemap ||= ActiveRecordTypeMap.new(conn)
end
def map_exec(klass = OpenStruct, args = {})
results = exec(args)
results.type_map = SqlBuilder.pg_type_map
setters = results.fields.each_with_index.map do |f, index|
f.dup << "="
end
values = results.values
values.map! do |row|
mapped = klass.new
setters.each_with_index do |name, index|
mapped.send name, row[index]
end
mapped
end
end
end
class ActiveRecord::Base
def self.sql_builder(template)
SqlBuilder.new(template, self)
end
end