diff --git a/lib/freedom_patches/fast_pluck.rb b/lib/freedom_patches/fast_pluck.rb new file mode 100644 index 000000000..d365a614f --- /dev/null +++ b/lib/freedom_patches/fast_pluck.rb @@ -0,0 +1,108 @@ +# Speeds up #pluck so its about 2.2x faster, importantly makes pluck avoid creation of a slew +# of AR objects + +require_dependency 'sql_builder' + +class ActiveRecord::Relation + + # class RailsDateTimeDecoder < PG::SimpleDecoder + # def decode(string, tuple=nil, field=nil) + # if Rails.version >= "4.2.0" + # @caster ||= ActiveRecord::Type::DateTime.new + # @caster.type_cast_from_database(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 + + class ::ActiveRecord::ConnectionAdapters::PostgreSQLAdapter + if Rails.version >= "4.2.0" + def select_raw(arel, name = nil, binds = [], &block) + arel, binds = binds_from_relation arel, binds + sql = to_sql(arel, binds) + execute_and_clear(sql, name, binds, &block) + end + else + + def select_raw(arel, name = nil, binds = [], &block) + arel, binds = binds_from_relation arel, binds + sql = to_sql(arel, binds) + + result = without_prepared_statement?(binds) ? exec_no_cache(sql, 'SQL', binds) : + exec_cache(sql, 'SQL', binds) + yield result, nil + end + end + end + + def pluck(*cols) + + conn = ActiveRecord::Base.connection + relation = self + + cols.map! do |column_name| + if column_name.is_a?(Symbol) && attribute_alias?(column_name) + attribute_alias(column_name) + else + column_name.to_s + end + end + + + if has_include?(cols.first) + construct_relation_for_association_calculations.pluck(*cols) + else + relation = spawn + + relation.select_values = cols.map { |cn| + columns_hash.key?(cn) ? arel_table[cn] : cn + } + + conn.select_raw(relation) do |result,_| + result.type_map = SqlBuilder.pg_type_map + result.nfields == 1 ? result.column_values(0) : result.values + end + + end + end +end + +# require 'benchmark/ips' +# +# ENV['RAILS_ENV'] = 'production' +# require File.expand_path("../../config/environment", __FILE__) +# +# Benchmark.ips do |x| +# x.report("fast_pluck") do +# Post.where(topic_id: 48464).fast_pluck(:id) +# end +# +# x.report("pluck") do +# Post.where(topic_id: 48464).pluck(:id) +# end +# end +# +# % ruby tmp/fast_pluck.rb +# Calculating ------------------------------------- +# fast_pluck 165.000 i/100ms +# pluck 80.000 i/100ms +# ------------------------------------------------- +# fast_pluck 1.720k (± 8.8%) i/s - 8.580k +# pluck 807.913 (± 4.0%) i/s - 4.080k +# diff --git a/lib/sql_builder.rb b/lib/sql_builder.rb index b12ed9c81..70e461095 100644 --- a/lib/sql_builder.rb +++ b/lib/sql_builder.rb @@ -36,9 +36,9 @@ class SqlBuilder when :where, :where2 joined = "WHERE " << v.map{|c| "(" << c << ")" }.join(" AND ") when :join - joined = v.map{|v| "JOIN " << v }.join("\n") + joined = v.map{|item| "JOIN " << item }.join("\n") when :left_join - joined = v.map{|v| "LEFT JOIN " << v }.join("\n") + joined = v.map{|item| "LEFT JOIN " << item }.join("\n") when :limit joined = "LIMIT " << v.last.to_s when :offset @@ -69,46 +69,50 @@ class SqlBuilder end end - #AS reloads this on tests - remove_const :FTYPE_MAP if defined? FTYPE_MAP - - if Rails.version >= "4.2.0" - FTYPE_MAP = { - 23 => ActiveRecord::Type::Integer.new, - 1114 => ActiveRecord::Type::DateTime.new, - 16 => ActiveRecord::Type::Boolean.new - } - else - FTYPE_MAP = { - 23 => :value_to_integer, - 1114 => :string_to_time, - 16 => :value_to_boolean - } - 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.type_cast_from_database(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 << "=").to_sym, FTYPE_MAP[results.ftype(index)]] + f.dup << "=" end + values = results.values values.map! do |row| mapped = klass.new - setters.each_with_index do |mapper, index| - translated = row[index] - if mapper[1] && !translated.nil? - if Rails.version >= "4.2.0" - translated = mapper[1].type_cast_from_database(translated) - else - translated = ActiveRecord::ConnectionAdapters::Column.send mapper[1], translated - end - end - mapped.send mapper[0], translated + setters.each_with_index do |name, index| + mapped.send name, row[index] end mapped end diff --git a/lib/topic_query.rb b/lib/topic_query.rb index 4d0ba83c1..15af3dd51 100644 --- a/lib/topic_query.rb +++ b/lib/topic_query.rb @@ -137,7 +137,6 @@ class TopicQuery pinned_ids = query.where('pinned_at IS NOT NULL AND category_id = ?', category.id) .order('pinned_at DESC').pluck(:id) non_pinned_ids = query.where('pinned_at IS NULL OR category_id <> ?', category.id).pluck(:id) - (pinned_ids + non_pinned_ids)[0...@options[:per_page]] end diff --git a/spec/models/post_action_spec.rb b/spec/models/post_action_spec.rb index f64d92252..8bd4648c0 100644 --- a/spec/models/post_action_spec.rb +++ b/spec/models/post_action_spec.rb @@ -2,9 +2,6 @@ require 'spec_helper' require_dependency 'post_destroyer' describe PostAction do - it { is_expected.to belong_to :user } - it { is_expected.to belong_to :post } - it { is_expected.to belong_to :post_action_type } it { is_expected.to rate_limit } let(:moderator) { Fabricate(:moderator) } @@ -458,6 +455,7 @@ describe PostAction do end expect(topic.reload.closed).to eq(true) + end end