From 5f64fd0a217552d1e031a28732a0e6000b090cc4 Mon Sep 17 00:00:00 2001 From: Sam Date: Tue, 19 Jun 2018 16:13:14 +1000 Subject: [PATCH] DEV: remove exec_sql and replace with mini_sql Introduce new patterns for direct sql that are safe and fast. MiniSql is not prone to memory bloat that can happen with direct PG usage. It also has an extremely fast materializer and very a convenient API - DB.exec(sql, *params) => runs sql returns row count - DB.query(sql, *params) => runs sql returns usable objects (not a hash) - DB.query_hash(sql, *params) => runs sql returns an array of hashes - DB.query_single(sql, *params) => runs sql and returns a flat one dimensional array - DB.build(sql) => returns a sql builder See more at: https://github.com/discourse/mini_sql --- Gemfile | 3 +- Gemfile.lock | 6 +- .../admin/diagnostics_controller.rb | 13 - app/controllers/admin/users_controller.rb | 2 +- app/jobs/onceoff/create_tags_search_index.rb | 4 +- app/jobs/onceoff/fix_posts_read.rb | 2 +- .../fix_primary_emails_for_staged_users.rb | 2 +- app/jobs/onceoff/fix_retro_anniversary.rb | 8 +- app/jobs/onceoff/init_category_tag_stats.rb | 4 +- app/jobs/onceoff/migrate_censored_words.rb | 4 +- app/jobs/onceoff/retro_recent_time_read.rb | 2 +- app/jobs/regular/update_username.rb | 4 +- app/jobs/scheduled/clean_up_crawler_stats.rb | 2 +- .../scheduled/grant_anniversary_badges.rb | 5 +- .../grant_new_user_of_the_month_badges.rb | 7 +- app/models/badge.rb | 4 +- app/models/category.rb | 16 +- app/models/category_tag_stat.rb | 4 +- app/models/category_user.rb | 16 +- app/models/concerns/positionable.rb | 6 +- app/models/directory_item.rb | 39 +-- app/models/draft.rb | 8 +- app/models/draft_sequence.rb | 6 +- app/models/emoji_set_site_setting.rb | 2 +- app/models/group.rb | 56 ++-- app/models/group_user.rb | 8 +- app/models/incoming_link.rb | 4 +- app/models/notification.rb | 12 +- app/models/post.rb | 28 +- app/models/post_action.rb | 43 +-- app/models/post_revision.rb | 4 +- app/models/post_timing.rb | 23 +- app/models/quoted_post.rb | 4 +- app/models/screened_ip_address.rb | 16 +- app/models/stylesheet_cache.rb | 2 +- app/models/tag.rb | 14 +- app/models/top_topic.rb | 10 +- app/models/topic.rb | 252 +++++++++--------- app/models/topic_featured_users.rb | 2 +- app/models/topic_link.rb | 16 +- app/models/topic_user.rb | 55 ++-- app/models/trust_level3_requirements.rb | 2 +- app/models/user.rb | 34 +-- app/models/user_action.rb | 7 +- app/models/user_auth_token.rb | 4 +- app/models/user_option.rb | 12 +- app/models/user_stat.rb | 54 ++-- app/models/user_visit.rb | 26 +- app/serializers/topic_link_serializer.rb | 38 +-- app/services/badge_granter.rb | 109 ++++---- app/services/post_alerter.rb | 14 +- app/services/search_indexer.rb | 6 +- app/services/user_merger.rb | 28 +- app/services/user_updater.rb | 23 +- config/initializers/000-mini_sql.rb | 2 + config/routes.rb | 1 - db/fixtures/000_delayed_drops.rb | 2 +- db/fixtures/001_categories.rb | 12 +- db/fixtures/006_badges.rb | 2 +- db/fixtures/500_lounge_category.rb | 2 +- db/fixtures/501_meta_category.rb | 2 +- db/fixtures/502_staff_category.rb | 2 +- ...538_add_starred_at_to_forum_thread_user.rb | 2 +- ...0121123054127_make_post_number_distinct.rb | 2 +- .../20140120155706_add_lounge_category.rb | 12 +- .../20140122043508_add_meta_category.rb | 12 +- .../20140227201005_add_staff_category.rb | 14 +- ...111_init_fixed_category_positions_value.rb | 4 +- ...20115_google_openid_default_has_changed.rb | 12 +- ...45431_disable_external_auths_by_default.rb | 8 +- ...1193923_remove_email_in_address_setting.rb | 10 +- ...216112341_resolve_duplicate_group_names.rb | 4 +- ...8_fix_category_logo_and_background_urls.rb | 2 +- ...15151505_add_seen_at_to_user_auth_token.rb | 2 +- .../20170728012754_split_public_in_groups.rb | 2 +- ...308071922_drop_raise_read_only_function.rb | 2 +- lib/backup_restore/backup_restore.rb | 4 +- lib/backup_restore/restorer.rb | 4 +- lib/comment_migration.rb | 8 +- lib/cooked_post_processor.rb | 2 +- lib/migration/base_dropper.rb | 2 +- lib/migration/column_dropper.rb | 6 +- lib/migration/table_dropper.rb | 10 +- lib/mini_sql_multisite_connection.rb | 38 +++ lib/post_revisor.rb | 2 +- lib/search.rb | 8 +- lib/tasks/db.rake | 24 +- lib/tasks/import.rake | 41 ++- lib/tasks/posts.rake | 10 +- lib/tasks/uploads.rake | 2 +- lib/topic_view.rb | 6 +- lib/topics_bulk_action.rb | 2 +- script/import_scripts/base.rb | 20 +- script/import_scripts/ipboard3.rb | 2 +- script/import_scripts/jive_api.rb | 2 +- script/import_scripts/lithium.rb | 48 ++-- script/import_scripts/modx.rb | 4 +- script/import_scripts/stack_overflow.rb | 2 +- script/import_scripts/vbulletin.rb | 8 +- script/import_scripts/vbulletin5.rb | 2 +- .../concern/has_custom_fields_spec.rb | 10 +- .../concern/has_search_data_spec.rb | 8 +- spec/components/concern/positionable_spec.rb | 6 +- spec/components/concern/searchable_spec.rb | 8 +- .../migration/column_dropper_spec.rb | 30 +-- .../migration/table_dropper_spec.rb | 14 +- spec/components/search_spec.rb | 2 +- spec/jobs/toggle_topic_closed_spec.rb | 2 +- spec/models/group_spec.rb | 2 +- spec/models/post_spec.rb | 2 +- spec/models/topic_link_spec.rb | 2 +- spec/models/topic_user_spec.rb | 14 +- 112 files changed, 782 insertions(+), 763 deletions(-) create mode 100644 config/initializers/000-mini_sql.rb create mode 100644 lib/mini_sql_multisite_connection.rb diff --git a/Gemfile b/Gemfile index bec856bd2a6..006b9175f3f 100644 --- a/Gemfile +++ b/Gemfile @@ -78,7 +78,8 @@ gem 'omniauth-oauth2', require: false gem 'omniauth-google-oauth2' gem 'oj' -gem 'pg', '~> 0.21.0' +gem 'pg' +gem 'mini_sql' gem 'pry-rails', require: false gem 'r2', '~> 0.2.5', require: false gem 'rake' diff --git a/Gemfile.lock b/Gemfile.lock index f4bea23c8fd..32b04e5d0df 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -176,6 +176,7 @@ GEM mini_portile2 (2.3.0) mini_racer (0.1.15) libv8 (~> 6.3) + mini_sql (0.1.4) mini_suffix (0.3.0) ffi (~> 1.9) minitest (5.11.3) @@ -240,7 +241,7 @@ GEM parallel (1.12.1) parser (2.5.1.0) ast (~> 2.4.0) - pg (0.21.0) + pg (1.0.0) powerpack (0.1.1) progress (3.4.0) pry (0.10.4) @@ -453,6 +454,7 @@ DEPENDENCIES message_bus mini_mime mini_racer + mini_sql mini_suffix minitest mocha @@ -471,7 +473,7 @@ DEPENDENCIES omniauth-twitter onebox (= 1.8.48) openid-redis-store - pg (~> 0.21.0) + pg pry-nav pry-rails puma diff --git a/app/controllers/admin/diagnostics_controller.rb b/app/controllers/admin/diagnostics_controller.rb index d10c559b4cd..81b076b0725 100644 --- a/app/controllers/admin/diagnostics_controller.rb +++ b/app/controllers/admin/diagnostics_controller.rb @@ -4,19 +4,6 @@ class Admin::DiagnosticsController < Admin::AdminController layout false skip_before_action :check_xhr - def dump_statement_cache - statements = Post.exec_sql("select * from pg_prepared_statements").to_a - text = "" - - statements.each do |row| - text << "name: #{row["name"]} sql: #{row["statement"]}\n" - end - - text << "\n\nCOUNT #{statements.count}" - - render plain: text - end - def memory_stats text = nil diff --git a/app/controllers/admin/users_controller.rb b/app/controllers/admin/users_controller.rb index 66b5daf9267..70573a2e918 100644 --- a/app/controllers/admin/users_controller.rb +++ b/app/controllers/admin/users_controller.rb @@ -77,7 +77,7 @@ class Admin::UsersController < Admin::AdminController ) SQL - UserHistory.exec_sql( + DB.exec( sql, UserHistory.actions.slice( :silence_user, diff --git a/app/jobs/onceoff/create_tags_search_index.rb b/app/jobs/onceoff/create_tags_search_index.rb index 3e673bf2e69..4101aed0dc6 100644 --- a/app/jobs/onceoff/create_tags_search_index.rb +++ b/app/jobs/onceoff/create_tags_search_index.rb @@ -1,8 +1,8 @@ module Jobs class CreateTagsSearchIndex < Jobs::Onceoff def execute_onceoff(args) - Tag.exec_sql('select id, name from tags').each do |t| - SearchIndexer.update_tags_index(t['id'], t['name']) + DB.query('select id, name from tags').each do |t| + SearchIndexer.update_tags_index(t.id, t.name) end end end diff --git a/app/jobs/onceoff/fix_posts_read.rb b/app/jobs/onceoff/fix_posts_read.rb index 2505038723b..0f8d97918ae 100644 --- a/app/jobs/onceoff/fix_posts_read.rb +++ b/app/jobs/onceoff/fix_posts_read.rb @@ -15,7 +15,7 @@ UPDATE user_stats ) SQL - UserStat.exec_sql(sql) + DB.exec(sql) end end end diff --git a/app/jobs/onceoff/fix_primary_emails_for_staged_users.rb b/app/jobs/onceoff/fix_primary_emails_for_staged_users.rb index d024688da91..c9516e36553 100644 --- a/app/jobs/onceoff/fix_primary_emails_for_staged_users.rb +++ b/app/jobs/onceoff/fix_primary_emails_for_staged_users.rb @@ -24,7 +24,7 @@ module Jobs end end - User.exec_sql <<~SQL + DB.exec <<~SQL INSERT INTO user_emails ( user_id, email, diff --git a/app/jobs/onceoff/fix_retro_anniversary.rb b/app/jobs/onceoff/fix_retro_anniversary.rb index 3cd4142caf4..2d553c3a296 100644 --- a/app/jobs/onceoff/fix_retro_anniversary.rb +++ b/app/jobs/onceoff/fix_retro_anniversary.rb @@ -6,7 +6,7 @@ module Jobs def execute_onceoff(args) return unless SiteSetting.enable_badges - users = User.exec_sql <<~SQL + users = User.query <<~SQL SELECT ub.user_id, MIN(granted_at) AS first_granted_at, COUNT(*) FROM user_badges AS ub WHERE ub.badge_id = #{Badge::Anniversary} @@ -15,17 +15,17 @@ module Jobs SQL users.to_a.each do |u| - first = Time.zone.parse(u['first_granted_at']) + first = u.first_granted_at badges = UserBadge.where( "badge_id = ? AND user_id = ? AND granted_at > ?", Badge::Anniversary, - u['user_id'], + u.user_id, first ).order('granted_at') badges.each_with_index do |b, idx| award_date = (first + (idx + 1).years) - UserBadge.where(id: b['id']).update_all(["granted_at = ?", award_date]) + UserBadge.where(id: b.id).update_all(["granted_at = ?", award_date]) end end diff --git a/app/jobs/onceoff/init_category_tag_stats.rb b/app/jobs/onceoff/init_category_tag_stats.rb index 43abdc0b429..e9f256d9b5c 100644 --- a/app/jobs/onceoff/init_category_tag_stats.rb +++ b/app/jobs/onceoff/init_category_tag_stats.rb @@ -1,9 +1,9 @@ module Jobs class InitCategoryTagStats < Jobs::Onceoff def execute_onceoff(args) - CategoryTagStat.exec_sql "DELETE FROM category_tag_stats" + DB.exec "DELETE FROM category_tag_stats" - CategoryTagStat.exec_sql <<~SQL + DB.exec <<~SQL INSERT INTO category_tag_stats (category_id, tag_id, topic_count) SELECT topics.category_id, tags.id, COUNT(topics.id) FROM tags diff --git a/app/jobs/onceoff/migrate_censored_words.rb b/app/jobs/onceoff/migrate_censored_words.rb index 03ce4b358cb..4f7a2201e72 100644 --- a/app/jobs/onceoff/migrate_censored_words.rb +++ b/app/jobs/onceoff/migrate_censored_words.rb @@ -1,9 +1,9 @@ module Jobs class MigrateCensoredWords < Jobs::Onceoff def execute_onceoff(args) - row = WatchedWord.exec_sql("SELECT value FROM site_settings WHERE name = 'censored_words'") + row = DB.query_single("SELECT value FROM site_settings WHERE name = 'censored_words'") if row.count > 0 - row.first["value"].split('|').each do |word| + row.first.split('|').each do |word| WatchedWord.create(word: word, action: WatchedWord.actions[:censor]) end end diff --git a/app/jobs/onceoff/retro_recent_time_read.rb b/app/jobs/onceoff/retro_recent_time_read.rb index 1dd1ce0289f..b7bf3e7501a 100644 --- a/app/jobs/onceoff/retro_recent_time_read.rb +++ b/app/jobs/onceoff/retro_recent_time_read.rb @@ -15,7 +15,7 @@ module Jobs AND EXISTS (SELECT 1 FROM user_visits visits WHERE visits.user_id = uv1.user_id AND visits.posts_read > 0 LIMIT 1) SQL - UserVisit.exec_sql(sql) + DB.exec(sql) end end end diff --git a/app/jobs/regular/update_username.rb b/app/jobs/regular/update_username.rb index c59275b7164..d3cfe48d78f 100644 --- a/app/jobs/regular/update_username.rb +++ b/app/jobs/regular/update_username.rb @@ -60,7 +60,7 @@ module Jobs new_username: @new_username } - Notification.exec_sql(<<~SQL, params) + DB.exec(<<~SQL, params) UPDATE notifications SET data = (data :: JSONB || jsonb_strip_nulls( @@ -88,7 +88,7 @@ module Jobs end def update_post_custom_fields - PostCustomField.exec_sql(<<~SQL, old_username: @old_username, new_username: @new_username) + DB.exec(<<~SQL, old_username: @old_username, new_username: @new_username) UPDATE post_custom_fields SET value = :new_username WHERE name = 'action_code_who' AND value = :old_username diff --git a/app/jobs/scheduled/clean_up_crawler_stats.rb b/app/jobs/scheduled/clean_up_crawler_stats.rb index 816e91f0fd2..5fbf5d548a3 100644 --- a/app/jobs/scheduled/clean_up_crawler_stats.rb +++ b/app/jobs/scheduled/clean_up_crawler_stats.rb @@ -7,7 +7,7 @@ module Jobs WebCrawlerRequest.where('date < ?', WebCrawlerRequest.max_record_age.ago).delete_all # keep count of only the top user agents - WebCrawlerRequest.exec_sql <<~SQL + DB.exec <<~SQL WITH ranked_requests AS ( SELECT row_number() OVER (ORDER BY count DESC) as row_number, id FROM web_crawler_requests diff --git a/app/jobs/scheduled/grant_anniversary_badges.rb b/app/jobs/scheduled/grant_anniversary_badges.rb index 65eb67423c6..91094a192ea 100644 --- a/app/jobs/scheduled/grant_anniversary_badges.rb +++ b/app/jobs/scheduled/grant_anniversary_badges.rb @@ -13,7 +13,7 @@ module Jobs fmt_end_date = end_date.iso8601(6) fmt_start_date = start_date.iso8601(6) - results = User.exec_sql <<~SQL + user_ids = DB.query_single <<~SQL SELECT u.id AS user_id FROM users AS u INNER JOIN posts AS p ON p.user_id = u.id @@ -33,9 +33,6 @@ module Jobs HAVING COUNT(p.id) > 0 AND COUNT(ub.id) = 0 SQL - user_ids = results.column_values(0) - results.clear - User.where(id: user_ids).find_each do |user| BadgeGranter.grant(badge, user, created_at: end_date) end diff --git a/app/jobs/scheduled/grant_new_user_of_the_month_badges.rb b/app/jobs/scheduled/grant_new_user_of_the_month_badges.rb index 097c3cd99f4..d6a4a16d56c 100644 --- a/app/jobs/scheduled/grant_new_user_of_the_month_badges.rb +++ b/app/jobs/scheduled/grant_new_user_of_the_month_badges.rb @@ -55,7 +55,7 @@ module Jobs ELSE 1.0 END ELSE 0 - END) / (5 + COUNT(DISTINCT p.id)) AS score + END) / (5 + COUNT(DISTINCT p.id))::float AS score FROM users AS u INNER JOIN user_stats AS us ON u.id = us.user_id LEFT OUTER JOIN posts AS p ON p.user_id = u.id @@ -82,10 +82,7 @@ module Jobs LIMIT #{MAX_AWARDED} SQL - result = User.exec_sql(sql) - rval = result.map { |r| [r['id'].to_i, r['score'].to_f] }.to_h - result.clear - rval + Hash[*DB.query_single(sql)] end end diff --git a/app/models/badge.rb b/app/models/badge.rb index 9a64949980b..ed73631eab8 100644 --- a/app/models/badge.rb +++ b/app/models/badge.rb @@ -136,7 +136,7 @@ class Badge < ActiveRecord::Base end def self.ensure_consistency! - exec_sql <<-SQL.squish + DB.exec <<~SQL DELETE FROM user_badges USING user_badges ub LEFT JOIN users u ON u.id = ub.user_id @@ -144,7 +144,7 @@ class Badge < ActiveRecord::Base AND user_badges.id = ub.id SQL - exec_sql <<-SQL.squish + DB.exec <<~SQL WITH X AS ( SELECT badge_id , COUNT(user_id) users diff --git a/app/models/category.rb b/app/models/category.rb index 379cc5acc7c..f0c4e4b8001 100644 --- a/app/models/category.rb +++ b/app/models/category.rb @@ -143,14 +143,14 @@ class Category < ActiveRecord::Base .group("topics.category_id") .visible.to_sql - Category.exec_sql <<-SQL - UPDATE categories c - SET topic_count = x.topic_count, - post_count = x.post_count - FROM (#{topics_with_post_count}) x - WHERE x.category_id = c.id - AND (c.topic_count <> x.topic_count OR c.post_count <> x.post_count) -SQL + DB.exec <<~SQL + UPDATE categories c + SET topic_count = x.topic_count, + post_count = x.post_count + FROM (#{topics_with_post_count}) x + WHERE x.category_id = c.id + AND (c.topic_count <> x.topic_count OR c.post_count <> x.post_count) + SQL # Yes, there are a lot of queries happening below. # Performing a lot of queries is actually faster than using one big update diff --git a/app/models/category_tag_stat.rb b/app/models/category_tag_stat.rb index 8aff0447c8d..b5673bcdf50 100644 --- a/app/models/category_tag_stat.rb +++ b/app/models/category_tag_stat.rb @@ -19,7 +19,7 @@ class CategoryTagStat < ActiveRecord::Base SQL tag_ids = topic.tags.map(&:id) - updated_tag_ids = self.exec_sql(sql, tag_ids: tag_ids, category_id: to_category_id).map { |row| row['tag_id'] } + updated_tag_ids = DB.query_single(sql, tag_ids: tag_ids, category_id: to_category_id) (tag_ids - updated_tag_ids).each do |tag_id| CategoryTagStat.create!(tag_id: tag_id, category_id: to_category_id, topic_count: 1) @@ -41,7 +41,7 @@ class CategoryTagStat < ActiveRecord::Base # Recalculate all topic counts if they got out of sync def self.update_topic_counts - CategoryTagStat.exec_sql <<~SQL + DB.exec <<~SQL UPDATE category_tag_stats stats SET topic_count = x.topic_count FROM ( diff --git a/app/models/category_user.rb b/app/models/category_user.rb index 8d0cf159127..0ae88ecd423 100644 --- a/app/models/category_user.rb +++ b/app/models/category_user.rb @@ -155,14 +155,14 @@ SQL end def self.ensure_consistency! - exec_sql < (self.position) - self.exec_sql " + DB.exec " UPDATE #{self.class.table_name} SET position = position - 1 WHERE position > :current_position and position <= :new_position", current_position: self.position, new_position: position elsif position < self.position - self.exec_sql " + DB.exec " UPDATE #{self.class.table_name} SET position = position + 1 WHERE position >= :new_position and position < :current_position", @@ -28,7 +28,7 @@ module Positionable return end - self.exec_sql " + DB.exec " UPDATE #{self.class.table_name} SET position = :position WHERE id = :id", id: id, position: position diff --git a/app/models/directory_item.rb b/app/models/directory_item.rb index a31453bc022..b60a2f19d86 100644 --- a/app/models/directory_item.rb +++ b/app/models/directory_item.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + class DirectoryItem < ActiveRecord::Base belongs_to :user has_one :user_stat, foreign_key: :user_id, primary_key: :user_id @@ -42,7 +44,7 @@ class DirectoryItem < ActiveRecord::Base ActiveRecord::Base.transaction do # Delete records that belonged to users who have been deleted - exec_sql "DELETE FROM directory_items + DB.exec "DELETE FROM directory_items USING directory_items di LEFT JOIN users u ON (u.id = user_id AND u.active AND u.silenced_till IS NULL AND u.id > 0) WHERE di.id = directory_items.id AND @@ -50,7 +52,7 @@ class DirectoryItem < ActiveRecord::Base di.period_type = :period_type", period_type: period_types[period_type] # Create new records for users who don't have one yet - exec_sql "INSERT INTO directory_items(period_type, user_id, likes_received, likes_given, topics_entered, days_visited, posts_read, topic_count, post_count) + DB.exec "INSERT INTO directory_items(period_type, user_id, likes_received, likes_given, topics_entered, days_visited, posts_read, topic_count, post_count) SELECT :period_type, u.id, @@ -72,7 +74,7 @@ class DirectoryItem < ActiveRecord::Base # TODO # WARNING: post_count is a wrong name, it should be reply_count (excluding topic post) # - exec_sql "WITH x AS (SELECT + DB.exec "WITH x AS (SELECT u.id user_id, SUM(CASE WHEN p.id IS NOT NULL AND t.id IS NOT NULL AND ua.action_type = :was_liked_type THEN 1 ELSE 0 END) likes_received, SUM(CASE WHEN p.id IS NOT NULL AND t.id IS NOT NULL AND ua.action_type = :like_type THEN 1 ELSE 0 END) likes_given, @@ -121,23 +123,22 @@ class DirectoryItem < ActiveRecord::Base regular_post_type: Post.types[:regular] if period_type == :all - exec_sql < d.likes_given OR - s.likes_received <> d.likes_received OR - s.topic_count <> d.topic_count OR - s.post_count <> d.post_count - ) - -SQL + FROM directory_items d + WHERE s.user_id = d.user_id AND + d.period_type = 1 AND + ( s.likes_given <> d.likes_given OR + s.likes_received <> d.likes_received OR + s.topic_count <> d.topic_count OR + s.post_count <> d.post_count + ) + SQL end end end diff --git a/app/models/draft.rb b/app/models/draft.rb index be412cc72a3..b008a6c51c9 100644 --- a/app/models/draft.rb +++ b/app/models/draft.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + class Draft < ActiveRecord::Base NEW_TOPIC = 'new_topic' NEW_PRIVATE_MESSAGE = 'new_private_message' @@ -7,7 +9,7 @@ class Draft < ActiveRecord::Base d = find_draft(user, key) if d return if d.sequence > sequence - exec_sql("UPDATE drafts + DB.exec("UPDATE drafts SET data = :data, sequence = :sequence, revisions = revisions + 1 @@ -15,6 +17,8 @@ class Draft < ActiveRecord::Base else Draft.create!(user_id: user.id, draft_key: key, data: data, sequence: sequence) end + + true end def self.get(user, key, sequence) @@ -40,7 +44,7 @@ class Draft < ActiveRecord::Base end def self.cleanup! - exec_sql("DELETE FROM drafts where sequence < ( + DB.exec("DELETE FROM drafts where sequence < ( SELECT max(s.sequence) from draft_sequences s WHERE s.draft_key = drafts.draft_key AND s.user_id = drafts.user_id diff --git a/app/models/draft_sequence.rb b/app/models/draft_sequence.rb index 460556d004d..9629e8402c0 100644 --- a/app/models/draft_sequence.rb +++ b/app/models/draft_sequence.rb @@ -11,7 +11,7 @@ class DraftSequence < ActiveRecord::Base c.sequence ||= 0 c.sequence += 1 c.save! - exec_sql("DELETE FROM drafts WHERE user_id = :user_id AND draft_key = :draft_key AND sequence < :sequence", draft_key: key, user_id: user_id, sequence: c.sequence) + DB.exec("DELETE FROM drafts WHERE user_id = :user_id AND draft_key = :draft_key AND sequence < :sequence", draft_key: key, user_id: user_id, sequence: c.sequence) c.sequence end @@ -22,8 +22,8 @@ class DraftSequence < ActiveRecord::Base user_id = user.id unless user.is_a?(Integer) # perf critical path - r = exec_sql('select sequence from draft_sequences where user_id = ? and draft_key = ?', user_id, key).values - r.length.zero? ? 0 : r[0][0] + r, _ = DB.query_single('select sequence from draft_sequences where user_id = ? and draft_key = ?', user_id, key) + r.to_i end end diff --git a/app/models/emoji_set_site_setting.rb b/app/models/emoji_set_site_setting.rb index 952bd37a0dc..003eb770e4a 100644 --- a/app/models/emoji_set_site_setting.rb +++ b/app/models/emoji_set_site_setting.rb @@ -12,7 +12,7 @@ class EmojiSetSiteSetting < EnumSiteSetting after = "/images/emoji/#{site_setting.value}/" Scheduler::Defer.later("Fix Emoji Links") do - Post.exec_sql("UPDATE posts SET cooked = REPLACE(cooked, :before, :after) WHERE cooked LIKE :like", + DB.exec("UPDATE posts SET cooked = REPLACE(cooked, :before, :after) WHERE cooked LIKE :like", before: before, after: after, like: "%#{before}%" diff --git a/app/models/group.rb b/app/models/group.rb index fe0c647e394..60cb1c0282a 100644 --- a/app/models/group.rb +++ b/app/models/group.rb @@ -296,7 +296,7 @@ class Group < ActiveRecord::Base "SELECT id FROM users WHERE id <= 0 OR trust_level < #{id - 10}" end - exec_sql <<-SQL + DB.exec <<-SQL DELETE FROM group_users USING (#{remove_subquery}) X WHERE group_id = #{group.id} @@ -318,7 +318,7 @@ class Group < ActiveRecord::Base "SELECT id FROM users WHERE id > 0" end - exec_sql <<-SQL + DB.exec <<-SQL INSERT INTO group_users (group_id, user_id, created_at, updated_at) SELECT #{group.id}, X.id, now(), now() FROM group_users @@ -341,7 +341,7 @@ class Group < ActiveRecord::Base end def self.reset_all_counters! - exec_sql <<-SQL + DB.exec <<-SQL WITH X AS ( SELECT group_id , COUNT(user_id) users @@ -362,7 +362,7 @@ class Group < ActiveRecord::Base end def self.refresh_has_messages! - exec_sql <<-SQL + DB.exec <<-SQL UPDATE groups g SET has_messages = false WHERE NOT EXISTS (SELECT tg.id FROM topic_allowed_groups tg @@ -534,7 +534,7 @@ class Group < ActiveRecord::Base ) SQL - Group.exec_sql(sql, group_id: self.id, user_ids: user_ids) + DB.exec(sql, group_id: self.id, user_ids: user_ids) user_attributes = {} @@ -551,7 +551,7 @@ class Group < ActiveRecord::Base end # update group user count - Group.exec_sql <<-SQL.squish + DB.exec <<~SQL UPDATE groups g SET user_count = (SELECT COUNT(gu.user_id) @@ -605,9 +605,10 @@ class Group < ActiveRecord::Base name_lower = self.name.downcase if self.will_save_change_to_name? && self.name_was&.downcase != name_lower - existing = Group.exec_sql( + + existing = DB.exec( User::USERNAME_EXISTS_SQL, username: name_lower - ).values.present? + ) > 0 if existing errors.add(:name, I18n.t("activerecord.errors.messages.taken")) @@ -649,15 +650,15 @@ class Group < ActiveRecord::Base return if new_record? && !self.title.present? if self.saved_change_to_title? - sql = <<-SQL.squish - UPDATE users - SET title = :title - WHERE (title = :title_was OR title = '' OR title IS NULL) - AND COALESCE(title,'') <> COALESCE(:title,'') - AND id IN (SELECT user_id FROM group_users WHERE group_id = :id) - SQL + sql = <<~SQL + UPDATE users + SET title = :title + WHERE (title = :title_was OR title = '' OR title IS NULL) + AND COALESCE(title,'') <> COALESCE(:title,'') + AND id IN (SELECT user_id FROM group_users WHERE group_id = :id) + SQL - self.class.exec_sql(sql, title: title, title_was: title_before_last_save, id: id) + DB.exec(sql, title: title, title_was: title_before_last_save, id: id) end end @@ -666,18 +667,19 @@ class Group < ActiveRecord::Base if self.saved_change_to_primary_group? sql = <<~SQL - UPDATE users - /*set*/ - /*where*/ - SQL + UPDATE users + /*set*/ + /*where*/ + SQL - builder = SqlBuilder.new(sql) - builder.where(" - id IN ( - SELECT user_id - FROM group_users - WHERE group_id = :id - )", id: id) + builder = DB.build(sql) + builder.where(<<~SQL, id: id) + id IN ( + SELECT user_id + FROM group_users + WHERE group_id = :id + ) + SQL if primary_group builder.set("primary_group_id = :id") diff --git a/app/models/group_user.rb b/app/models/group_user.rb index 5840b536649..ed528b3d3a0 100644 --- a/app/models/group_user.rb +++ b/app/models/group_user.rb @@ -25,7 +25,7 @@ class GroupUser < ActiveRecord::Base def set_primary_group if group.primary_group - self.class.exec_sql(" + DB.exec(" UPDATE users SET primary_group_id = :id WHERE id = :user_id", @@ -35,7 +35,7 @@ class GroupUser < ActiveRecord::Base end def remove_primary_group - self.class.exec_sql(" + DB.exec(" UPDATE users SET primary_group_id = NULL WHERE id = :user_id AND primary_group_id = :id", @@ -45,7 +45,7 @@ class GroupUser < ActiveRecord::Base def remove_title if group.title.present? - self.class.exec_sql(" + DB.exec(" UPDATE users SET title = NULL WHERE title = :title AND id = :id", id: user_id, title: group.title @@ -55,7 +55,7 @@ class GroupUser < ActiveRecord::Base def update_title if group.title.present? - self.class.exec_sql(" + DB.exec(" UPDATE users SET title = :title WHERE (title IS NULL OR title = '') AND id = :id", id: user_id, title: group.title diff --git a/app/models/incoming_link.rb b/app/models/incoming_link.rb index 0cc748a9a30..ef680dc4939 100644 --- a/app/models/incoming_link.rb +++ b/app/models/incoming_link.rb @@ -89,10 +89,10 @@ class IncomingLink < ActiveRecord::Base # Internal: Update appropriate link counts. def update_link_counts - exec_sql("UPDATE topics + DB.exec("UPDATE topics SET incoming_link_count = incoming_link_count + 1 WHERE id = (SELECT topic_id FROM posts where id = ?)", post_id) - exec_sql("UPDATE posts + DB.exec("UPDATE posts SET incoming_link_count = incoming_link_count + 1 WHERE id = ?", post_id) end diff --git a/app/models/notification.rb b/app/models/notification.rb index 8e213851bc7..7a294d81bf3 100644 --- a/app/models/notification.rb +++ b/app/models/notification.rb @@ -19,10 +19,10 @@ class Notification < ActiveRecord::Base after_commit :refresh_notification_count, on: [:create, :update, :destroy] def self.ensure_consistency! - Notification.exec_sql <<-SQL + DB.exec(<<~SQL, Notification.types[:private_message]) DELETE FROM notifications n - WHERE notification_type = #{Notification.types[:private_message]} + WHERE notification_type = ? AND NOT EXISTS ( SELECT 1 FROM posts p @@ -152,17 +152,15 @@ class Notification < ActiveRecord::Base if notifications.present? - ids = Notification.exec_sql(" + ids = DB.query_single(<<~SQL, count.to_i) SELECT n.id FROM notifications n WHERE n.notification_type = 6 AND n.user_id = #{user.id.to_i} AND NOT read ORDER BY n.id ASC - LIMIT #{count.to_i} - ").values.map do |x, _| - x.to_i - end + LIMIT ? + SQL if ids.length > 0 notifications += user diff --git a/app/models/post.rb b/app/models/post.rb index c797c471a88..4e04c736f26 100644 --- a/app/models/post.rb +++ b/app/models/post.rb @@ -669,17 +669,19 @@ class Post < ActiveRecord::Base end def reply_history(max_replies = 100, guardian = nil) - post_ids = Post.exec_sql("WITH RECURSIVE breadcrumb(id, reply_to_post_number) AS ( - SELECT p.id, p.reply_to_post_number FROM posts AS p - WHERE p.id = :post_id - UNION - SELECT p.id, p.reply_to_post_number FROM posts AS p, breadcrumb - WHERE breadcrumb.reply_to_post_number = p.post_number - AND p.topic_id = :topic_id - ) SELECT id from breadcrumb ORDER by id", post_id: id, topic_id: topic_id).to_a - - post_ids.map! { |r| r['id'].to_i } - .reject! { |post_id| post_id == id } + post_ids = DB.query_single(<<~SQL, post_id: id, topic_id: topic_id) + WITH RECURSIVE breadcrumb(id, reply_to_post_number) AS ( + SELECT p.id, p.reply_to_post_number FROM posts AS p + WHERE p.id = :post_id + UNION + SELECT p.id, p.reply_to_post_number FROM posts AS p, breadcrumb + WHERE breadcrumb.reply_to_post_number = p.post_number + AND p.topic_id = :topic_id + ) + SELECT id from breadcrumb + WHERE id <> :post_id + ORDER by id + SQL # [1,2,3][-10,-1] => nil post_ids = (post_ids[(0 - max_replies)..-1] || post_ids) @@ -741,11 +743,11 @@ class Post < ActiveRecord::Base def self.rebake_all_quoted_posts(user_id) return if user_id.blank? - Post.exec_sql <<-SQL + DB.exec(<<~SQL, user_id) WITH user_quoted_posts AS ( SELECT post_id FROM quoted_posts - WHERE quoted_post_id IN (SELECT id FROM posts WHERE user_id = #{user_id}) + WHERE quoted_post_id IN (SELECT id FROM posts WHERE user_id = ?) ) UPDATE posts SET baked_version = NULL diff --git a/app/models/post_action.rb b/app/models/post_action.rb index 479091d8afc..cf4d50d2dc2 100644 --- a/app/models/post_action.rb +++ b/app/models/post_action.rb @@ -340,7 +340,7 @@ SQL def self.copy(original_post, target_post) cols_to_copy = (column_names - %w{id post_id}).join(', ') - exec_sql <<~SQL + DB.exec <<~SQL INSERT INTO post_actions(post_id, #{cols_to_copy}) SELECT #{target_post.id}, #{cols_to_copy} FROM post_actions @@ -425,26 +425,29 @@ SQL # Returns the flag counts for a post, taking into account that some users # can weigh flags differently. def self.flag_counts_for(post_id) - flag_counts = exec_sql("SELECT SUM(CASE - WHEN pa.disagreed_at IS NULL AND pa.staff_took_action THEN :flags_required_to_hide_post - WHEN pa.disagreed_at IS NULL AND NOT pa.staff_took_action THEN 1 - ELSE 0 - END) AS new_flags, - SUM(CASE - WHEN pa.disagreed_at IS NOT NULL AND pa.staff_took_action THEN :flags_required_to_hide_post - WHEN pa.disagreed_at IS NOT NULL AND NOT pa.staff_took_action THEN 1 - ELSE 0 - END) AS old_flags - FROM post_actions AS pa - INNER JOIN users AS u ON u.id = pa.user_id - WHERE pa.post_id = :post_id - AND pa.post_action_type_id IN (:post_action_types) - AND pa.deleted_at IS NULL", - post_id: post_id, - post_action_types: PostActionType.auto_action_flag_types.values, - flags_required_to_hide_post: SiteSetting.flags_required_to_hide_post).first + params = { + post_id: post_id, + post_action_types: PostActionType.auto_action_flag_types.values, + flags_required_to_hide_post: SiteSetting.flags_required_to_hide_post + } - [flag_counts['old_flags'].to_i, flag_counts['new_flags'].to_i] + DB.query_single(<<~SQL, params) + SELECT COALESCE(SUM(CASE + WHEN pa.disagreed_at IS NOT NULL AND pa.staff_took_action THEN :flags_required_to_hide_post + WHEN pa.disagreed_at IS NOT NULL AND NOT pa.staff_took_action THEN 1 + ELSE 0 + END),0) AS old_flags, + COALESCE(SUM(CASE + WHEN pa.disagreed_at IS NULL AND pa.staff_took_action THEN :flags_required_to_hide_post + WHEN pa.disagreed_at IS NULL AND NOT pa.staff_took_action THEN 1 + ELSE 0 + END), 0) AS new_flags + FROM post_actions AS pa + INNER JOIN users AS u ON u.id = pa.user_id + WHERE pa.post_id = :post_id + AND pa.post_action_type_id in (:post_action_types) + AND pa.deleted_at IS NULL + SQL end def post_action_type_key diff --git a/app/models/post_revision.rb b/app/models/post_revision.rb index 0a7016966dd..b91f50765a2 100644 --- a/app/models/post_revision.rb +++ b/app/models/post_revision.rb @@ -10,7 +10,7 @@ class PostRevision < ActiveRecord::Base def self.ensure_consistency! # 1 - fix the numbers - PostRevision.exec_sql <<-SQL + DB.exec <<-SQL UPDATE post_revisions SET number = pr.rank FROM (SELECT id, 1 + ROW_NUMBER() OVER (PARTITION BY post_id ORDER BY number, created_at, updated_at) AS rank FROM post_revisions) AS pr @@ -19,7 +19,7 @@ class PostRevision < ActiveRecord::Base SQL # 2 - fix the versions on the posts - PostRevision.exec_sql <<-SQL + DB.exec <<-SQL UPDATE posts SET version = 1 + (SELECT COUNT(*) FROM post_revisions WHERE post_id = posts.id), public_version = 1 + (SELECT COUNT(*) FROM post_revisions pr WHERE post_id = posts.id AND pr.hidden = 'f') diff --git a/app/models/post_timing.rb b/app/models/post_timing.rb index 54ed9bbefcf..23d224c5e64 100644 --- a/app/models/post_timing.rb +++ b/app/models/post_timing.rb @@ -12,7 +12,7 @@ class PostTiming < ActiveRecord::Base def self.pretend_read(topic_id, actual_read_post_number, pretend_read_post_number) # This is done in SQL cause the logic is quite tricky and we want to do this in one db hit # - exec_sql("INSERT INTO post_timings(topic_id, user_id, post_number, msecs) + DB.exec("INSERT INTO post_timings(topic_id, user_id, post_number, msecs) SELECT :topic_id, user_id, :pretend_read_post_number, 1 FROM post_timings pt WHERE topic_id = :topic_id AND @@ -34,7 +34,7 @@ class PostTiming < ActiveRecord::Base def self.record_new_timing(args) begin - exec_sql("INSERT INTO post_timings (topic_id, user_id, post_number, msecs) + DB.exec("INSERT INTO post_timings (topic_id, user_id, post_number, msecs) SELECT :topic_id, :user_id, :post_number, :msecs WHERE NOT EXISTS(SELECT 1 FROM post_timings WHERE topic_id = :topic_id @@ -53,12 +53,13 @@ class PostTiming < ActiveRecord::Base # Increases a timer if a row exists, otherwise create it def self.record_timing(args) - rows = exec_sql_row_count("UPDATE post_timings - SET msecs = msecs + :msecs - WHERE topic_id = :topic_id - AND user_id = :user_id - AND post_number = :post_number", - args) + rows = DB.exec(<<~SQL, args) + UPDATE post_timings + SET msecs = msecs + :msecs + WHERE topic_id = :topic_id + AND user_id = :user_id + AND post_number = :post_number + SQL record_new_timing(args) if rows == 0 end @@ -115,9 +116,7 @@ class PostTiming < ActiveRecord::Base RETURNING x.idx SQL - result = exec_sql(sql) - result.type_map = SqlBuilder.pg_type_map - existing = Set.new(result.column_values(0)) + existing = Set.new(DB.query_single(sql)) sql = <<~SQL SELECT 1 FROM topics @@ -126,7 +125,7 @@ SQL id = :topic_id SQL - is_regular = Post.exec_sql(sql, topic_id: topic_id).cmd_tuples == 1 + is_regular = DB.exec(sql, topic_id: topic_id) == 1 new_posts_read = timings.size - existing.size if is_regular timings.each_with_index do |(post_number, time), index| diff --git a/app/models/quoted_post.rb b/app/models/quoted_post.rb index c6d24675f5b..26cc80ef8d9 100644 --- a/app/models/quoted_post.rb +++ b/app/models/quoted_post.rb @@ -11,7 +11,7 @@ class QuotedPost < ActiveRecord::Base uniq = {} - exec_sql("DELETE FROM quoted_posts WHERE post_id = :post_id", post_id: post.id) + DB.exec("DELETE FROM quoted_posts WHERE post_id = :post_id", post_id: post.id) doc.css("aside.quote[data-topic]").each do |a| topic_id = a['data-topic'].to_i @@ -23,7 +23,7 @@ class QuotedPost < ActiveRecord::Base begin # It would be so much nicer if we used post_id in quotes - exec_sql(<<~SQL, post_id: post.id, post_number: post_number, topic_id: topic_id) + DB.exec(<<~SQL, post_id: post.id, post_number: post_number, topic_id: topic_id) INSERT INTO quoted_posts (post_id, quoted_post_id, created_at, updated_at) SELECT :post_id, p.id, current_timestamp, current_timestamp FROM posts p diff --git a/app/models/screened_ip_address.rb b/app/models/screened_ip_address.rb index 0c23b574b4b..29729e622e2 100644 --- a/app/models/screened_ip_address.rb +++ b/app/models/screened_ip_address.rb @@ -94,8 +94,8 @@ class ScreenedIpAddress < ActiveRecord::Base end def self.star_subnets_query - @star_subnets_query ||= <<-SQL - SELECT network(inet(host(ip_address) || '/24')) AS ip_range + @star_subnets_query ||= <<~SQL + SELECT network(inet(host(ip_address) || '/24'))::text AS ip_range FROM screened_ip_addresses WHERE action_type = #{ScreenedIpAddress.actions[:block]} AND family(ip_address) = 4 @@ -106,9 +106,9 @@ class ScreenedIpAddress < ActiveRecord::Base end def self.star_star_subnets_query - @star_star_subnets_query ||= <<-SQL + @star_star_subnets_query ||= <<~SQL WITH weighted_subnets AS ( - SELECT network(inet(host(ip_address) || '/16')) AS ip_range, + SELECT network(inet(host(ip_address) || '/16'))::text AS ip_range, CASE masklen(ip_address) WHEN 32 THEN 1 WHEN 24 THEN :roll_up_weight @@ -127,12 +127,12 @@ class ScreenedIpAddress < ActiveRecord::Base def self.star_subnets min_count = SiteSetting.min_ban_entries_for_roll_up - ScreenedIpAddress.exec_sql(star_subnets_query, min_count: min_count).values.flatten + DB.query_single(star_subnets_query, min_count: min_count) end def self.star_star_subnets weight = SiteSetting.min_ban_entries_for_roll_up - ScreenedIpAddress.exec_sql(star_star_subnets_query, min_count: 10, roll_up_weight: weight).values.flatten + DB.query_single(star_star_subnets_query, min_count: 10, roll_up_weight: weight) end def self.roll_up(current_user = Discourse.system_user) @@ -143,7 +143,7 @@ class ScreenedIpAddress < ActiveRecord::Base subnets.each do |subnet| ScreenedIpAddress.create(ip_address: subnet) unless ScreenedIpAddress.where("? <<= ip_address", subnet).exists? - sql = <<-SQL + sql = <<~SQL UPDATE screened_ip_addresses SET match_count = sum_match_count , created_at = min_created_at @@ -160,7 +160,7 @@ class ScreenedIpAddress < ActiveRecord::Base WHERE ip_address = :ip_address SQL - ScreenedIpAddress.exec_sql(sql, ip_address: subnet) + DB.exec(sql, ip_address: subnet) ScreenedIpAddress.where(action_type: ScreenedIpAddress.actions[:block]) .where("family(ip_address) = 4") diff --git a/app/models/stylesheet_cache.rb b/app/models/stylesheet_cache.rb index 08b98c30859..52f6ef337e6 100644 --- a/app/models/stylesheet_cache.rb +++ b/app/models/stylesheet_cache.rb @@ -24,7 +24,7 @@ class StylesheetCache < ActiveRecord::Base .pluck(:id) .last - exec_sql("DELETE FROM stylesheet_cache where id < :id", id: remove_lower) + DB.exec("DELETE FROM stylesheet_cache where id < :id", id: remove_lower) end success diff --git a/app/models/tag.rb b/app/models/tag.rb index 46ef77bde7b..705d6768a5b 100644 --- a/app/models/tag.rb +++ b/app/models/tag.rb @@ -25,7 +25,7 @@ class Tag < ActiveRecord::Base end def self.update_topic_counts - Tag.exec_sql <<~SQL + DB.exec <<~SQL UPDATE tags t SET topic_count = x.topic_count FROM ( @@ -41,7 +41,7 @@ class Tag < ActiveRecord::Base AND x.topic_count <> t.topic_count SQL - Tag.exec_sql <<~SQL + DB.exec <<~SQL UPDATE tags t SET pm_topic_count = x.pm_topic_count FROM ( @@ -70,7 +70,7 @@ class Tag < ActiveRecord::Base filter_sql = guardian&.is_staff? ? '' : " AND tags.id NOT IN (#{DiscourseTagging.hidden_tags_query.select(:id).to_sql})" - tag_names_with_counts = Tag.exec_sql <<~SQL + tag_names_with_counts = DB.query <<~SQL SELECT tags.name as tag_name, SUM(stats.topic_count) AS sum_topic_count FROM category_tag_stats stats JOIN tags ON stats.tag_id = tags.id AND stats.topic_count > 0 @@ -81,7 +81,7 @@ class Tag < ActiveRecord::Base LIMIT #{limit} SQL - tag_names_with_counts.map { |row| row['tag_name'] } + tag_names_with_counts.map { |row| row.tag_name } end def self.pm_tags(limit_arg: nil, guardian: nil, allowed_user: nil) @@ -89,8 +89,8 @@ class Tag < ActiveRecord::Base limit = limit_arg || SiteSetting.max_tags_in_filter_list user_id = allowed_user.id - tag_names_with_counts = Tag.exec_sql <<~SQL - SELECT tags.name, COUNT(topics.id) AS topic_count + DB.query_hash(<<~SQL).map!(&:symbolize_keys!) + SELECT tags.name as id, tags.name as text, COUNT(topics.id) AS count FROM tags JOIN topic_tags ON tags.id = topic_tags.tag_id JOIN topics ON topics.id = topic_tags.topic_id @@ -109,8 +109,6 @@ class Tag < ActiveRecord::Base GROUP BY tags.name LIMIT #{limit} SQL - - tag_names_with_counts.map { |t| { id: t['name'], text: t['name'], count: t['topic_count'] } } end def self.include_tags? diff --git a/app/models/top_topic.rb b/app/models/top_topic.rb index 709148167c4..58703023b46 100644 --- a/app/models/top_topic.rb +++ b/app/models/top_topic.rb @@ -59,7 +59,7 @@ class TopTopic < ActiveRecord::Base end def self.remove_invisible_topics - exec_sql("WITH category_definition_topic_ids AS ( + DB.exec("WITH category_definition_topic_ids AS ( SELECT COALESCE(topic_id, 0) AS id FROM categories ), invisible_topic_ids AS ( SELECT id @@ -76,7 +76,7 @@ class TopTopic < ActiveRecord::Base end def self.add_new_visible_topics - exec_sql("WITH category_definition_topic_ids AS ( + DB.exec("WITH category_definition_topic_ids AS ( SELECT COALESCE(topic_id, 0) AS id FROM categories ), visible_topics AS ( SELECT t.id @@ -167,7 +167,7 @@ class TopTopic < ActiveRecord::Base time_filter = "topics.created_at < :from" end - sql = <<-SQL + sql = <<~SQL WITH top AS ( SELECT CASE WHEN #{time_filter} THEN 0 @@ -197,7 +197,7 @@ class TopTopic < ActiveRecord::Base AND #{period}_score <> top.score SQL - exec_sql(sql, from: start_of(period)) + DB.exec(sql, from: start_of(period)) end def self.start_of(period) @@ -211,7 +211,7 @@ class TopTopic < ActiveRecord::Base end def self.update_top_topics(period, sort, inner_join) - exec_sql("UPDATE top_topics + DB.exec("UPDATE top_topics SET #{period}_#{sort}_count = c.count FROM top_topics tt INNER JOIN (#{inner_join}) c ON tt.topic_id = c.topic_id diff --git a/app/models/topic.rb b/app/models/topic.rb index 9b1c1145ea0..b0e1e628018 100644 --- a/app/models/topic.rb +++ b/app/models/topic.rb @@ -352,7 +352,7 @@ class Topic < ActiveRecord::Base if !new_record? && !Discourse.readonly_mode? # make sure data is set in table, this also allows us to change algorithm # by simply nulling this column - exec_sql("UPDATE topics SET fancy_title = :fancy_title where id = :id", id: self.id, fancy_title: fancy_title) + DB.exec("UPDATE topics SET fancy_title = :fancy_title where id = :id", id: self.id, fancy_title: fancy_title) end end @@ -522,130 +522,140 @@ class Topic < ActiveRecord::Base # Atomically creates the next post number def self.next_post_number(topic_id, reply = false, whisper = false) - highest = exec_sql("SELECT coalesce(max(post_number),0) AS max FROM posts WHERE topic_id = ?", topic_id).first['max'].to_i + highest = DB.query_single("SELECT coalesce(max(post_number),0) AS max FROM posts WHERE topic_id = ?", topic_id).first.to_i if whisper - result = exec_sql("UPDATE topics - SET highest_staff_post_number = ? + 1 - WHERE id = ? - RETURNING highest_staff_post_number", highest, topic_id) + result = DB.query_single(<<~SQL, highest, topic_id) + UPDATE topics + SET highest_staff_post_number = ? + 1 + WHERE id = ? + RETURNING highest_staff_post_number + SQL - result.first['highest_staff_post_number'].to_i + result.first.to_i else reply_sql = reply ? ", reply_count = reply_count + 1" : "" - result = exec_sql("UPDATE topics - SET highest_staff_post_number = :highest + 1, - highest_post_number = :highest + 1#{reply_sql}, - posts_count = posts_count + 1 - WHERE id = :topic_id - RETURNING highest_post_number", highest: highest, topic_id: topic_id) + result = DB.query_single(<<~SQL, highest: highest, topic_id: topic_id) + UPDATE topics + SET highest_staff_post_number = :highest + 1, + highest_post_number = :highest + 1#{reply_sql}, + posts_count = posts_count + 1 + WHERE id = :topic_id + RETURNING highest_post_number + SQL - result.first['highest_post_number'].to_i + result.first.to_i end end def self.reset_all_highest! - exec_sql < 4 - GROUP BY topic_id -) -UPDATE topics -SET - highest_staff_post_number = X.highest_post_number, - highest_post_number = Y.highest_post_number, - last_posted_at = Y.last_posted_at, - posts_count = Y.posts_count -FROM X, Y -WHERE - X.topic_id = topics.id AND - Y.topic_id = topics.id AND ( - topics.highest_staff_post_number <> X.highest_post_number OR - topics.highest_post_number <> Y.highest_post_number OR - topics.last_posted_at <> Y.last_posted_at OR - topics.posts_count <> Y.posts_count - ) -SQL + DB.exec <<~SQL + WITH + X as ( + SELECT topic_id, + COALESCE(MAX(post_number), 0) highest_post_number + FROM posts + WHERE deleted_at IS NULL + GROUP BY topic_id + ), + Y as ( + SELECT topic_id, + coalesce(MAX(post_number), 0) highest_post_number, + count(*) posts_count, + max(created_at) last_posted_at + FROM posts + WHERE deleted_at IS NULL AND post_type <> 4 + GROUP BY topic_id + ) + UPDATE topics + SET + highest_staff_post_number = X.highest_post_number, + highest_post_number = Y.highest_post_number, + last_posted_at = Y.last_posted_at, + posts_count = Y.posts_count + FROM X, Y + WHERE + X.topic_id = topics.id AND + Y.topic_id = topics.id AND ( + topics.highest_staff_post_number <> X.highest_post_number OR + topics.highest_post_number <> Y.highest_post_number OR + topics.last_posted_at <> Y.last_posted_at OR + topics.posts_count <> Y.posts_count + ) + SQL end # If a post is deleted we have to update our highest post counters def self.reset_highest(topic_id) - result = exec_sql "UPDATE topics - SET - highest_staff_post_number = ( - SELECT COALESCE(MAX(post_number), 0) FROM posts - WHERE topic_id = :topic_id AND - deleted_at IS NULL - ), - highest_post_number = ( - SELECT COALESCE(MAX(post_number), 0) FROM posts - WHERE topic_id = :topic_id AND - deleted_at IS NULL AND - post_type <> 4 - ), - posts_count = ( - SELECT count(*) FROM posts - WHERE deleted_at IS NULL AND - topic_id = :topic_id AND - post_type <> 4 - ), + result = DB.query_single(<<~SQL, topic_id: topic_id) + UPDATE topics + SET + highest_staff_post_number = ( + SELECT COALESCE(MAX(post_number), 0) FROM posts + WHERE topic_id = :topic_id AND + deleted_at IS NULL + ), + highest_post_number = ( + SELECT COALESCE(MAX(post_number), 0) FROM posts + WHERE topic_id = :topic_id AND + deleted_at IS NULL AND + post_type <> 4 + ), + posts_count = ( + SELECT count(*) FROM posts + WHERE deleted_at IS NULL AND + topic_id = :topic_id AND + post_type <> 4 + ), - last_posted_at = ( - SELECT MAX(created_at) FROM posts - WHERE topic_id = :topic_id AND - deleted_at IS NULL AND - post_type <> 4 - ) - WHERE id = :topic_id - RETURNING highest_post_number", topic_id: topic_id + last_posted_at = ( + SELECT MAX(created_at) FROM posts + WHERE topic_id = :topic_id AND + deleted_at IS NULL AND + post_type <> 4 + ) + WHERE id = :topic_id + RETURNING highest_post_number + SQL - highest_post_number = result.first['highest_post_number'].to_i + highest_post_number = result.first.to_i # Update the forum topic user records - exec_sql "UPDATE topic_users - SET last_read_post_number = CASE - WHEN last_read_post_number > :highest THEN :highest - ELSE last_read_post_number - END, - highest_seen_post_number = CASE - WHEN highest_seen_post_number > :highest THEN :highest - ELSE highest_seen_post_number - END - WHERE topic_id = :topic_id", - highest: highest_post_number, - topic_id: topic_id + DB.exec(<<~SQL, highest: highest_post_number, topic_id: topic_id) + UPDATE topic_users + SET last_read_post_number = CASE + WHEN last_read_post_number > :highest THEN :highest + ELSE last_read_post_number + END, + highest_seen_post_number = CASE + WHEN highest_seen_post_number > :highest THEN :highest + ELSE highest_seen_post_number + END + WHERE topic_id = :topic_id + SQL end # This calculates the geometric mean of the posts and stores it with the topic def self.calculate_avg_time(min_topic_age = nil) - builder = SqlBuilder.new("UPDATE topics - SET avg_time = x.gmean - FROM (SELECT topic_id, - round(exp(avg(ln(avg_time)))) AS gmean - FROM posts - WHERE avg_time > 0 AND avg_time IS NOT NULL - GROUP BY topic_id) AS x - /*where*/") + builder = DB.build <<~SQL + UPDATE topics + SET avg_time = x.gmean + FROM (SELECT topic_id, + round(exp(avg(ln(avg_time)))) AS gmean + FROM posts + WHERE avg_time > 0 AND avg_time IS NOT NULL + GROUP BY topic_id) AS x + /*where*/ + SQL - builder.where("x.topic_id = topics.id AND - (topics.avg_time <> x.gmean OR topics.avg_time IS NULL)") + builder.where <<~SQL + x.topic_id = topics.id AND + (topics.avg_time <> x.gmean OR topics.avg_time IS NULL) + SQL if min_topic_age builder.where("topics.bumped_at > :bumped_at", bumped_at: min_topic_age) @@ -1179,30 +1189,30 @@ SQL # OR if you have it archived as a user explicitly sql = <<~SQL - SELECT 1 - WHERE - ( - SELECT count(*) FROM topic_allowed_groups tg - JOIN group_archived_messages gm - ON gm.topic_id = tg.topic_id AND - gm.group_id = tg.group_id - WHERE tg.group_id IN (SELECT g.group_id FROM group_users g WHERE g.user_id = :user_id) - AND tg.topic_id = :topic_id - ) = - ( - SELECT case when count(*) = 0 then -1 else count(*) end FROM topic_allowed_groups tg - WHERE tg.group_id IN (SELECT g.group_id FROM group_users g WHERE g.user_id = :user_id) - AND tg.topic_id = :topic_id - ) + SELECT 1 + WHERE + ( + SELECT count(*) FROM topic_allowed_groups tg + JOIN group_archived_messages gm + ON gm.topic_id = tg.topic_id AND + gm.group_id = tg.group_id + WHERE tg.group_id IN (SELECT g.group_id FROM group_users g WHERE g.user_id = :user_id) + AND tg.topic_id = :topic_id + ) = + ( + SELECT case when count(*) = 0 then -1 else count(*) end FROM topic_allowed_groups tg + WHERE tg.group_id IN (SELECT g.group_id FROM group_users g WHERE g.user_id = :user_id) + AND tg.topic_id = :topic_id + ) - UNION ALL + UNION ALL - SELECT 1 FROM topic_allowed_users tu - JOIN user_archived_messages um ON um.user_id = tu.user_id AND um.topic_id = tu.topic_id - WHERE tu.user_id = :user_id AND tu.topic_id = :topic_id -SQL + SELECT 1 FROM topic_allowed_users tu + JOIN user_archived_messages um ON um.user_id = tu.user_id AND um.topic_id = tu.topic_id + WHERE tu.user_id = :user_id AND tu.topic_id = :topic_id + SQL - User.exec_sql(sql, user_id: user.id, topic_id: id).to_a.length > 0 + DB.exec(sql, user_id: user.id, topic_id: id) > 0 end TIME_TO_FIRST_RESPONSE_SQL ||= <<-SQL @@ -1325,8 +1335,8 @@ SQL ) = 1 SQL - result = Topic.exec_sql(sql, private_message: Archetype.private_message, topic_id: self.id) - result.ntuples != 0 + result = DB.exec(sql, private_message: Archetype.private_message, topic_id: self.id) + result != 0 end def featured_link_root_domain diff --git a/app/models/topic_featured_users.rb b/app/models/topic_featured_users.rb index d8a8a406cb1..7b442423b24 100644 --- a/app/models/topic_featured_users.rb +++ b/app/models/topic_featured_users.rb @@ -79,7 +79,7 @@ WHERE tt.id = tt2.id AND #{filter2} SQL - Topic.exec_sql(sql) + DB.exec(sql) end private diff --git a/app/models/topic_link.rb b/app/models/topic_link.rb index 40fbcab8010..c7a745218d1 100644 --- a/app/models/topic_link.rb +++ b/app/models/topic_link.rb @@ -38,7 +38,7 @@ class TopicLink < ActiveRecord::Base def self.topic_map(guardian, topic_id) # Sam: complicated reports are really hard in AR - builder = SqlBuilder.new <<-SQL + builder = DB.build <<-SQL SELECT ftl.url, COALESCE(ft.title, ftl.title) AS title, ftl.link_topic_id, @@ -64,16 +64,16 @@ SQL builder.secure_category(guardian.secure_category_ids) - builder.exec.to_a + builder.query end def self.counts_for(guardian, topic, posts) return {} if posts.blank? - # Sam: I don't know how to write this cleanly in AR, - # in particular the securing logic is tricky and would fallback to SQL anyway - builder = SqlBuilder.new("SELECT + # Sam: this is not tidy in AR and also happens to be a critical path + # for topic view + builder = DB.build("SELECT l.post_id, l.url, l.clicks, @@ -91,10 +91,11 @@ SQL builder.where("COALESCE(t.archetype, 'regular') <> :archetype", archetype: Archetype.private_message) # not certain if pluck is right, cause it may interfere with caching - builder.where('l.post_id IN (:post_ids)', post_ids: posts.map(&:id)) + builder.where('l.post_id in (:post_ids)', post_ids: posts.map(&:id)) builder.secure_category(guardian.secure_category_ids) - builder.map_exec(OpenStruct).each_with_object({}) do |l, result| + result = {} + builder.query.each do |l| result[l.post_id] ||= [] result[l.post_id] << { url: l.url, clicks: l.clicks, @@ -102,6 +103,7 @@ SQL internal: l.internal, reflection: l.reflection } end + result end def self.extract_from(post) diff --git a/app/models/topic_user.rb b/app/models/topic_user.rb index a6cff083ded..b41fabb3daa 100644 --- a/app/models/topic_user.rb +++ b/app/models/topic_user.rb @@ -53,26 +53,26 @@ class TopicUser < ActiveRecord::Base def unwatch_categories!(user, category_ids) track_threshold = user.user_option.auto_track_topics_after_msecs - sql = < :track_threshold AND :track_threshold >= 0 THEN :tracking - ELSE :regular - end - FROM topics t - WHERE t.id = tu.topic_id AND tu.notification_level <> :muted AND category_id IN (:category_ids) AND tu.user_id = :user_id -SQL + sql = <<~SQL + UPDATE topic_users tu + SET notification_level = CASE + WHEN t.user_id = :user_id THEN :watching + WHEN total_msecs_viewed > :track_threshold AND :track_threshold >= 0 THEN :tracking + ELSE :regular + end + FROM topics t + WHERE t.id = tu.topic_id AND tu.notification_level <> :muted AND category_id IN (:category_ids) AND tu.user_id = :user_id + SQL - exec_sql(sql, - watching: notification_levels[:watching], - tracking: notification_levels[:tracking], - regular: notification_levels[:regular], - muted: notification_levels[:muted], - category_ids: category_ids, - user_id: user.id, - track_threshold: track_threshold - ) + DB.exec(sql, + watching: notification_levels[:watching], + tracking: notification_levels[:tracking], + regular: notification_levels[:regular], + muted: notification_levels[:muted], + category_ids: category_ids, + user_id: user.id, + track_threshold: track_threshold + ) end # Find the information specific to a user in a forum topic @@ -296,16 +296,15 @@ SQL # 86400000 = 1 day rows = if user.staff? - exec_sql(UPDATE_TOPIC_USER_SQL_STAFF, args).values + DB.query(UPDATE_TOPIC_USER_SQL_STAFF, args) else - exec_sql(UPDATE_TOPIC_USER_SQL, args).values + DB.query(UPDATE_TOPIC_USER_SQL, args) end if rows.length == 1 - before = rows[0][1].to_i - after = rows[0][0].to_i - - before_last_read = rows[0][2].to_i + before = rows[0].old_level.to_i + after = rows[0].notification_level.to_i + before_last_read = rows[0].last_read_post_number.to_i if before_last_read < post_number # The user read at least one new post @@ -333,9 +332,9 @@ SQL begin if user.staff? - exec_sql(INSERT_TOPIC_USER_SQL_STAFF, args) + DB.exec(INSERT_TOPIC_USER_SQL_STAFF, args) else - exec_sql(INSERT_TOPIC_USER_SQL, args) + DB.exec(INSERT_TOPIC_USER_SQL, args) end rescue PG::UniqueViolation # if record is inserted between two statements this can happen @@ -431,7 +430,7 @@ SQL ) SQL - TopicUser.exec_sql(sql, user_id: user_id, count: count) + DB.exec(sql, user_id: user_id, count: count) end def self.ensure_consistency!(topic_id = nil) diff --git a/app/models/trust_level3_requirements.rb b/app/models/trust_level3_requirements.rb index ac63e5ae6aa..7b856905087 100644 --- a/app/models/trust_level3_requirements.rb +++ b/app/models/trust_level3_requirements.rb @@ -122,7 +122,7 @@ class TrustLevel3Requirements AND uh.action IN (:silence_user, :unsilence_user, :suspend_user, :unsuspend_user) SQL - PenaltyCounts.new(UserHistory.exec_sql(sql, args).first) + PenaltyCounts.new(DB.query_hash(sql, args).first) end def min_days_visited diff --git a/app/models/user.rb b/app/models/user.rb index a20687a5d1c..e389a4ea4ce 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -208,7 +208,7 @@ class User < ActiveRecord::Base def self.username_available?(username, email = nil) lower = username.downcase return false if reserved_username?(lower) - return true if User.exec_sql(User::USERNAME_EXISTS_SQL, username: lower).count == 0 + return true if DB.exec(User::USERNAME_EXISTS_SQL, username: lower) == 0 # staged users can use the same username since they will take over the account email.present? && User.joins(:user_emails).exists?(staged: true, username_lower: lower, user_emails: { primary: true, email: email }) @@ -387,7 +387,8 @@ class User < ActiveRecord::Base AND NOT read SQL - User.exec_sql(sql, user_id: id, type: notification_type).getvalue(0, 0).to_i + # to avoid coalesce we do to_i + DB.query_single(sql, user_id: id, type: notification_type)[0].to_i end def unread_private_messages @@ -408,11 +409,11 @@ class User < ActiveRecord::Base AND NOT read SQL - User.exec_sql(sql, + DB.query_single(sql, user_id: id, seen_notification_id: seen_notification_id, pm: Notification.types[:private_message] - ).getvalue(0, 0).to_i + )[0].to_i end end @@ -446,7 +447,7 @@ class User < ActiveRecord::Base notification = notifications.visible.order('notifications.id desc').first json = NotificationSerializer.new(notification).as_json if notification - sql = " + sql = (<<~SQL).freeze SELECT * FROM ( SELECT n.id, n.read FROM notifications n LEFT JOIN topics t ON n.topic_id = t.id @@ -469,13 +470,13 @@ class User < ActiveRecord::Base ORDER BY n.id DESC LIMIT 20 ) AS y - " + SQL - recent = User.exec_sql(sql, + recent = DB.query(sql, user_id: id, type: Notification.types[:private_message] - ).values.map! do |id, read| - [id.to_i, read] + ).map! do |r| + [r.id, r.read] end payload = { @@ -1155,12 +1156,12 @@ class User < ActiveRecord::Base end USERNAME_EXISTS_SQL = <<~SQL - (SELECT users.id AS user_id FROM users + (SELECT users.id AS id, true as is_user FROM users WHERE users.username_lower = :username) UNION ALL - (SELECT groups.id AS group_id FROM groups + (SELECT groups.id, false as is_user FROM groups WHERE lower(groups.name) = :username) SQL @@ -1168,11 +1169,14 @@ class User < ActiveRecord::Base username_format_validator || begin lower = username.downcase - existing = User.exec_sql( + existing = DB.query( USERNAME_EXISTS_SQL, username: lower - ).to_a.first + ) - if will_save_change_to_username? && existing.present? && existing["user_id"] != self.id + user_id = existing.select { |u| u.is_user }.first&.id + same_user = user_id && user_id == self.id + + if will_save_change_to_username? && existing.present? && !same_user errors.add(:username, I18n.t(:'user.username.unique')) end end @@ -1200,7 +1204,7 @@ class User < ActiveRecord::Base end if values.present? - exec_sql("INSERT INTO category_users (user_id, category_id, notification_level) VALUES #{values.join(",")}") + DB.exec("INSERT INTO category_users (user_id, category_id, notification_level) VALUES #{values.join(",")}") end end diff --git a/app/models/user_action.rb b/app/models/user_action.rb index f36c034b0ec..981cb4a9c44 100644 --- a/app/models/user_action.rb +++ b/app/models/user_action.rb @@ -97,7 +97,8 @@ SQL AND t.id IN (SELECT topic_id FROM topic_allowed_users WHERE user_id = :user_id) SQL - all, mine, unread = exec_sql(sql, user_id: user_id).values[0].map(&:to_i) + # map is there due to count returning nil + all, mine, unread = DB.query_single(sql, user_id: user_id).map(&:to_i) sql = <<-SQL SELECT g.name, COUNT(*) "count" @@ -112,8 +113,8 @@ SQL result = { all: all, mine: mine, unread: unread } - exec_sql(sql, user_id: user_id).each do |row| - (result[:groups] ||= []) << { name: row["name"], count: row["count"].to_i } + DB.query(sql, user_id: user_id).each do |row| + (result[:groups] ||= []) << { name: row.name, count: row.count.to_i } end result diff --git a/app/models/user_auth_token.rb b/app/models/user_auth_token.rb index 57eeaca8882..b7549486cab 100644 --- a/app/models/user_auth_token.rb +++ b/app/models/user_auth_token.rb @@ -131,7 +131,7 @@ class UserAuthToken < ActiveRecord::Base token = SecureRandom.hex(16) - result = UserAuthToken.exec_sql(" + result = DB.exec(" UPDATE user_auth_tokens SET auth_token_seen = false, @@ -150,7 +150,7 @@ class UserAuthToken < ActiveRecord::Base safeguard_time: 30.seconds.ago ) - if result.cmdtuples > 0 + if result > 0 reload self.unhashed_auth_token = token diff --git a/app/models/user_option.rb b/app/models/user_option.rb index 5077d8dc364..95c34d51f6a 100644 --- a/app/models/user_option.rb +++ b/app/models/user_option.rb @@ -6,10 +6,14 @@ class UserOption < ActiveRecord::Base after_save :update_tracked_topics def self.ensure_consistency! - exec_sql("SELECT u.id FROM users u - LEFT JOIN user_options o ON o.user_id = u.id - WHERE o.user_id IS NULL").values.each do |id, _| - UserOption.create(user_id: id.to_i) + sql = <<~SQL + SELECT u.id FROM users u + LEFT JOIN user_options o ON o.user_id = u.id + WHERE o.user_id IS NULL + SQL + + DB.query_single(sql).each do |id| + UserOption.create(user_id: id) end end diff --git a/app/models/user_stat.rb b/app/models/user_stat.rb index cf9ab05e2ca..86731032a39 100644 --- a/app/models/user_stat.rb +++ b/app/models/user_stat.rb @@ -23,34 +23,36 @@ class UserStat < ActiveRecord::Base # we also ensure we only touch the table if data changes # Update denormalized topics_entered - exec_sql "UPDATE user_stats SET topics_entered = X.c - FROM - (SELECT v.user_id, COUNT(topic_id) AS c - FROM topic_views AS v - WHERE v.user_id IN ( - SELECT u1.id FROM users u1 where u1.last_seen_at > :seen_at - ) - GROUP BY v.user_id) AS X - WHERE - X.user_id = user_stats.user_id AND - X.c <> topics_entered - ", seen_at: last_seen + DB.exec(<<~SQL, seen_at: last_seen) + UPDATE user_stats SET topics_entered = X.c + FROM + (SELECT v.user_id, COUNT(topic_id) AS c + FROM topic_views AS v + WHERE v.user_id IN ( + SELECT u1.id FROM users u1 where u1.last_seen_at > :seen_at + ) + GROUP BY v.user_id) AS X + WHERE + X.user_id = user_stats.user_id AND + X.c <> topics_entered + SQL # Update denormalzied posts_read_count - exec_sql "UPDATE user_stats SET posts_read_count = X.c - FROM - (SELECT pt.user_id, - COUNT(*) AS c - FROM users AS u - JOIN post_timings AS pt ON pt.user_id = u.id - JOIN topics t ON t.id = pt.topic_id - WHERE u.last_seen_at > :seen_at AND - t.archetype = 'regular' AND - t.deleted_at IS NULL - GROUP BY pt.user_id) AS X - WHERE X.user_id = user_stats.user_id AND - X.c <> posts_read_count - ", seen_at: last_seen + DB.exec(<<~SQL, seen_at: last_seen) + UPDATE user_stats SET posts_read_count = X.c + FROM + (SELECT pt.user_id, + COUNT(*) AS c + FROM users AS u + JOIN post_timings AS pt ON pt.user_id = u.id + JOIN topics t ON t.id = pt.topic_id + WHERE u.last_seen_at > :seen_at AND + t.archetype = 'regular' AND + t.deleted_at IS NULL + GROUP BY pt.user_id) AS X + WHERE X.user_id = user_stats.user_id AND + X.c <> posts_read_count + SQL end # topic_reply_count is a count of posts in other users' topics diff --git a/app/models/user_visit.rb b/app/models/user_visit.rb index 52bd2fab9c6..6fe21eae7bb 100644 --- a/app/models/user_visit.rb +++ b/app/models/user_visit.rb @@ -11,7 +11,7 @@ class UserVisit < ActiveRecord::Base end def self.count_by_active_users(start_date, end_date) - sql = < - ( - SELECT COUNT(*) FROM user_visits v WHERE v.user_id = u.user_id - ) -SQL + DB.exec <<~SQL + UPDATE user_stats u set days_visited = + ( + SELECT COUNT(*) FROM user_visits v WHERE v.user_id = u.user_id + ) + WHERE days_visited <> + ( + SELECT COUNT(*) FROM user_visits v WHERE v.user_id = u.user_id + ) + SQL end end diff --git a/app/serializers/topic_link_serializer.rb b/app/serializers/topic_link_serializer.rb index 42367857c54..4ef9afc65b0 100644 --- a/app/serializers/topic_link_serializer.rb +++ b/app/serializers/topic_link_serializer.rb @@ -2,7 +2,7 @@ class TopicLinkSerializer < ApplicationSerializer attributes :url, :title, - :fancy_title, + # :fancy_title, :internal, :attachment, :reflection, @@ -11,44 +11,12 @@ class TopicLinkSerializer < ApplicationSerializer :domain, :root_domain, - def url - object['url'] - end - - def title - object['title'] - end - - def fancy_title - object['fancy_title'] - end - - def internal - object['internal'] == 't' - end - def attachment - Discourse.store.has_been_uploaded?(object['url']) - end - - def reflection - object['reflection'] == 't' - end - - def clicks - object['clicks'].to_i - end - - def user_id - object['user_id'].to_i + Discourse.store.has_been_uploaded?(object.url) end def include_user_id? - object['user_id'].present? - end - - def domain - object['domain'] + object.user_id.present? end def root_domain diff --git a/app/services/badge_granter.rb b/app/services/badge_granter.rb index 1ac69bce482..8446b5bcda7 100644 --- a/app/services/badge_granter.rb +++ b/app/services/badge_granter.rb @@ -204,17 +204,18 @@ class BadgeGranter end query_plan = nil - # HACK: active record is weird, force it to go down the sanitization path that cares not for % stuff - query_plan = ActiveRecord::Base.exec_sql("EXPLAIN #{sql} /*:backfill*/", params) if opts[:explain] + # HACK: active record sanitization too flexible, force it to go down the sanitization path that cares not for % stuff + # note mini_sql uses AR sanitizer at the moment (review if changed) + query_plan = DB.query_hash("EXPLAIN #{sql} /*:backfill*/", params) if opts[:explain] - sample = SqlBuilder.map_exec(OpenStruct, grants_sql, params).map(&:to_h) + sample = DB.query(grants_sql, params) sample.each do |result| - raise "Query returned a non-existent user ID:\n#{result[:id]}" unless User.find(result[:id]).present? - raise "Query did not return a badge grant time\n(Try using 'current_timestamp granted_at')" unless result[:granted_at] + raise "Query returned a non-existent user ID:\n#{result.id}" unless User.exists?(id: result.id) + raise "Query did not return a badge grant time\n(Try using 'current_timestamp granted_at')" unless result.granted_at if opts[:target_posts] - raise "Query did not return a post ID" unless result[:post_id] - raise "Query returned a non-existent post ID:\n#{result[:post_id]}" unless Post.find(result[:post_id]).present? + raise "Query did not return a post ID" unless result.post_id + raise "Query returned a non-existent post ID:\n#{result.post_id}" unless Post.exists?(result.post_id).present? end end @@ -258,28 +259,31 @@ class BadgeGranter WHERE ub.badge_id = :id AND q.user_id IS NULL )" - Badge.exec_sql(sql, id: badge.id, - post_ids: [-1], - user_ids: [-2], - backfill: true, - multiple_grant: true # cheat here, cause we only run on backfill and are deleting - ) if badge.auto_revoke && full_backfill + DB.exec( + sql, + id: badge.id, + post_ids: [-1], + user_ids: [-2], + backfill: true, + multiple_grant: true # cheat here, cause we only run on backfill and are deleting + ) if badge.auto_revoke && full_backfill - sql = " WITH w as ( - INSERT INTO user_badges(badge_id, user_id, granted_at, granted_by_id, post_id) - SELECT :id, q.user_id, q.granted_at, -1, #{post_id_field} - FROM ( #{badge.query} ) q - LEFT JOIN user_badges ub ON - ub.badge_id = :id AND ub.user_id = q.user_id - #{post_clause} - /*where*/ - RETURNING id, user_id, granted_at - ) - select w.*, username, locale, (u.admin OR u.moderator) AS staff FROM w - JOIN users u on u.id = w.user_id - " + sql = <<~SQL + WITH w as ( + INSERT INTO user_badges(badge_id, user_id, granted_at, granted_by_id, post_id) + SELECT :id, q.user_id, q.granted_at, -1, #{post_id_field} + FROM ( #{badge.query} ) q + LEFT JOIN user_badges ub ON + ub.badge_id = :id AND ub.user_id = q.user_id + #{post_clause} + /*where*/ + RETURNING id, user_id, granted_at + ) + select w.*, username, locale, (u.admin OR u.moderator) AS staff FROM w + JOIN users u on u.id = w.user_id + SQL - builder = SqlBuilder.new(sql) + builder = DB.build(sql) builder.where("ub.badge_id IS NULL AND q.user_id <> -1") if (post_ids || user_ids) && !badge.query.include?(":backfill") @@ -297,11 +301,12 @@ class BadgeGranter return end - builder.map_exec(OpenStruct, id: badge.id, - multiple_grant: badge.multiple_grant, - backfill: full_backfill, - post_ids: post_ids || [-2], - user_ids: user_ids || [-2]).each do |row| + builder.query( + id: badge.id, + multiple_grant: badge.multiple_grant, + backfill: full_backfill, + post_ids: post_ids || [-2], + user_ids: user_ids || [-2]).each do |row| # old bronze badges do not matter next if badge.badge_type_id == (BadgeType::Bronze) && row.granted_at < (2.days.ago) @@ -332,10 +337,11 @@ class BadgeGranter }.to_json) end - Badge.exec_sql("UPDATE user_badges SET notification_id = :notification_id WHERE id = :id", - notification_id: notification.id, - id: row.id - ) + DB.exec( + "UPDATE user_badges SET notification_id = :notification_id WHERE id = :id", + notification_id: notification.id, + id: row.id + ) end badge.reset_grant_count! @@ -345,21 +351,22 @@ class BadgeGranter end def self.revoke_ungranted_titles! - Badge.exec_sql("UPDATE users SET title = '' - WHERE NOT title IS NULL AND - title <> '' AND - EXISTS ( - SELECT 1 - FROM user_profiles - WHERE user_id = users.id AND badge_granted_title - ) AND - title NOT IN ( - SELECT name - FROM badges - WHERE allow_title AND enabled AND - badges.id IN (SELECT badge_id FROM user_badges ub where ub.user_id = users.id) - ) - ") + DB.exec <<~SQL + UPDATE users SET title = '' + WHERE NOT title IS NULL AND + title <> '' AND + EXISTS ( + SELECT 1 + FROM user_profiles + WHERE user_id = users.id AND badge_granted_title + ) AND + title NOT IN ( + SELECT name + FROM badges + WHERE allow_title AND enabled AND + badges.id IN (SELECT badge_id FROM user_badges ub where ub.user_id = users.id) + ) + SQL end end diff --git a/app/services/post_alerter.rb b/app/services/post_alerter.rb index 476856253b8..9b4e774b053 100644 --- a/app/services/post_alerter.rb +++ b/app/services/post_alerter.rb @@ -194,16 +194,18 @@ class PostAlerter } def group_stats(topic) + sql = <<~SQL + SELECT COUNT(*) FROM topics t + JOIN topic_allowed_groups g ON g.group_id = :group_id AND g.topic_id = t.id + LEFT JOIN group_archived_messages a ON a.topic_id = t.id AND a.group_id = g.group_id + WHERE a.id IS NULL AND t.deleted_at is NULL AND t.archetype = 'private_message' + SQL + topic.allowed_groups.map do |g| { group_id: g.id, group_name: g.name.downcase, - inbox_count: Topic.exec_sql( - "SELECT COUNT(*) FROM topics t - JOIN topic_allowed_groups g ON g.group_id = :group_id AND g.topic_id = t.id - LEFT JOIN group_archived_messages a ON a.topic_id = t.id AND a.group_id = g.group_id - WHERE a.id IS NULL AND t.deleted_at is NULL AND t.archetype = 'private_message'", - group_id: g.id).values[0][0].to_i + inbox_count: DB.query_single(sql, group_id: g.id).first.to_i } end end diff --git a/app/services/search_indexer.rb b/app/services/search_indexer.rb index 35ccd7a378b..6165be07058 100644 --- a/app/services/search_indexer.rb +++ b/app/services/search_indexer.rb @@ -61,7 +61,7 @@ class SearchIndexer # Would be nice to use AR here but not sure how to execut Postgres functions # when inserting data like this. - rows = Post.exec_sql_row_count(<<~SQL, params) + rows = DB.exec(<<~SQL, params) UPDATE #{table_name} SET raw_data = :raw_data, @@ -72,7 +72,7 @@ class SearchIndexer SQL if rows == 0 - Post.exec_sql(<<~SQL, params) + DB.exec(<<~SQL, params) INSERT INTO #{table_name} (#{foreign_key}, search_data, locale, raw_data, version) VALUES (:id, #{ranked_index}, :locale, :raw_data, :version) @@ -111,7 +111,7 @@ class SearchIndexer def self.queue_post_reindex(topic_id) return if @disabled - ActiveRecord::Base.exec_sql(<<~SQL, topic_id: topic_id) + DB.exec(<<~SQL, topic_id: topic_id) UPDATE post_search_data SET version = 0 WHERE post_id IN (SELECT id FROM posts WHERE topic_id = :topic_id) diff --git a/app/services/user_merger.rb b/app/services/user_merger.rb index e1d590926bb..4fc678b13f8 100644 --- a/app/services/user_merger.rb +++ b/app/services/user_merger.rb @@ -89,11 +89,13 @@ class UserMerger limit_reached = EXCLUDED.limit_reached SQL - GivenDailyLike.exec_sql(sql, - source_user_id: @source_user.id, - target_user_id: @target_user.id, - max_likes_per_day: SiteSetting.max_likes_per_day, - action_type_id: PostActionType.types[:like]) + DB.exec( + sql, + source_user_id: @source_user.id, + target_user_id: @target_user.id, + max_likes_per_day: SiteSetting.max_likes_per_day, + action_type_id: PostActionType.types[:like] + ) end def merge_post_timings @@ -107,7 +109,7 @@ class UserMerger AND t.topic_id = s.topic_id AND t.post_number = s.post_number SQL - PostTiming.exec_sql(sql, source_user_id: @source_user.id, target_user_id: @target_user.id) + DB.exec(sql, source_user_id: @source_user.id, target_user_id: @target_user.id) end def merge_user_visits @@ -123,7 +125,7 @@ class UserMerger AND t.visited_at = s.visited_at SQL - UserVisit.exec_sql(sql, source_user_id: @source_user.id, target_user_id: @target_user.id) + DB.exec(sql, source_user_id: @source_user.id, target_user_id: @target_user.id) end def update_site_settings @@ -136,7 +138,7 @@ class UserMerger def update_user_stats # topics_entered - UserStat.exec_sql(<<~SQL, target_user_id: @target_user.id) + DB.exec(<<~SQL, target_user_id: @target_user.id) UPDATE user_stats SET topics_entered = ( SELECT COUNT(topic_id) @@ -147,7 +149,7 @@ class UserMerger SQL # time_read and days_visited - UserStat.exec_sql(<<~SQL, target_user_id: @target_user.id) + DB.exec(<<~SQL, target_user_id: @target_user.id) UPDATE user_stats SET time_read = COALESCE(x.time_read, 0), days_visited = COALESCE(x.days_visited, 0) @@ -162,7 +164,7 @@ class UserMerger SQL # posts_read_count - UserStat.exec_sql(<<~SQL, target_user_id: @target_user.id) + DB.exec(<<~SQL, target_user_id: @target_user.id) UPDATE user_stats SET posts_read_count = ( SELECT COUNT(1) @@ -176,7 +178,7 @@ class UserMerger SQL # likes_given, likes_received, new_since, read_faq, first_post_created_at - UserStat.exec_sql(<<~SQL, source_user_id: @source_user.id, target_user_id: @target_user.id) + DB.exec(<<~SQL, source_user_id: @source_user.id, target_user_id: @target_user.id) UPDATE user_stats AS t SET likes_given = t.likes_given + s.likes_given, likes_received = t.likes_received + s.likes_received, @@ -189,7 +191,7 @@ class UserMerger end def merge_user_attributes - User.exec_sql(<<~SQL, source_user_id: @source_user.id, target_user_id: @target_user.id) + DB.exec(<<~SQL, source_user_id: @source_user.id, target_user_id: @target_user.id) UPDATE users AS t SET created_at = LEAST(t.created_at, s.created_at), updated_at = LEAST(t.updated_at, s.updated_at), @@ -213,7 +215,7 @@ class UserMerger WHERE t.id = :target_user_id AND s.id = :source_user_id SQL - UserProfile.exec_sql(<<~SQL, source_user_id: @source_user.id, target_user_id: @target_user.id) + DB.exec(<<~SQL, source_user_id: @source_user.id, target_user_id: @target_user.id) UPDATE user_profiles AS t SET location = COALESCE(t.location, s.location), website = COALESCE(t.website, s.website), diff --git a/app/services/user_updater.rb b/app/services/user_updater.rb index ade3e97db9f..eb9011cc21f 100644 --- a/app/services/user_updater.rb +++ b/app/services/user_updater.rb @@ -143,17 +143,18 @@ class UserUpdater MutedUser.where('user_id = ? AND muted_user_id not in (?)', user.id, desired_ids).destroy_all # SQL is easier here than figuring out how to do the same in AR - MutedUser.exec_sql("INSERT into muted_users(user_id, muted_user_id, created_at, updated_at) - SELECT :user_id, id, :now, :now - FROM users - WHERE - id in (:desired_ids) AND - id NOT IN ( - SELECT muted_user_id - FROM muted_users - WHERE user_id = :user_id - )", - now: Time.now, user_id: user.id, desired_ids: desired_ids) + DB.exec(<<~SQL, now: Time.now, user_id: user.id, desired_ids: desired_ids) + INSERT into muted_users(user_id, muted_user_id, created_at, updated_at) + SELECT :user_id, id, :now, :now + FROM users + WHERE + id in (:desired_ids) AND + id NOT IN ( + SELECT muted_user_id + FROM muted_users + WHERE user_id = :user_id + ) + SQL end end diff --git a/config/initializers/000-mini_sql.rb b/config/initializers/000-mini_sql.rb new file mode 100644 index 00000000000..35f8713b8bf --- /dev/null +++ b/config/initializers/000-mini_sql.rb @@ -0,0 +1,2 @@ +require 'mini_sql_multisite_connection' +::DB = MiniSqlMultisiteConnection.instance diff --git a/config/routes.rb b/config/routes.rb index 030fd7aad2d..0923afeab04 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -288,7 +288,6 @@ Discourse::Application.routes.draw do get "memory_stats" => "diagnostics#memory_stats", constraints: AdminConstraint.new get "dump_heap" => "diagnostics#dump_heap", constraints: AdminConstraint.new - get "dump_statement_cache" => "diagnostics#dump_statement_cache", constraints: AdminConstraint.new end # admin namespace get "email_preferences" => "email#preferences_redirect", :as => "email_preferences_redirect" diff --git a/db/fixtures/000_delayed_drops.rb b/db/fixtures/000_delayed_drops.rb index bf07dcb7251..a6a5eebde36 100644 --- a/db/fixtures/000_delayed_drops.rb +++ b/db/fixtures/000_delayed_drops.rb @@ -48,7 +48,7 @@ Migration::ColumnDropper.drop( }, on_drop: ->() { STDERR.puts "Removing superflous user stats columns!" - ActiveRecord::Base.exec_sql "DROP FUNCTION IF EXISTS first_unread_topic_for(int)" + DB.exec "DROP FUNCTION IF EXISTS first_unread_topic_for(int)" } ) diff --git a/db/fixtures/001_categories.rb b/db/fixtures/001_categories.rb index aef949349cd..982eee7dad8 100644 --- a/db/fixtures/001_categories.rb +++ b/db/fixtures/001_categories.rb @@ -10,18 +10,18 @@ uncat_id = -1 unless Numeric === uncat_id if uncat_id == -1 || !Category.exists?(uncat_id) puts "Seeding uncategorized category!" - result = Category.exec_sql "SELECT 1 FROM categories WHERE lower(name) = 'uncategorized'" + count = DB.exec "SELECT 1 FROM categories WHERE lower(name) = 'uncategorized'" name = 'Uncategorized' - name << SecureRandom.hex if result.count > 0 + name << SecureRandom.hex if count > 0 - result = Category.exec_sql "INSERT INTO categories + result = DB.query_single "INSERT INTO categories (name,color,slug,description,text_color, user_id, created_at, updated_at, position, name_lower) VALUES ('#{name}', 'AB9364', 'uncategorized', '', 'FFFFFF', -1, now(), now(), 1, '#{name.downcase}' ) RETURNING id " - category_id = result[0]["id"].to_i + category_id = result.first.to_i - Category.exec_sql "DELETE FROM site_settings where name = 'uncategorized_category_id'" - Category.exec_sql "INSERT INTO site_settings(name, data_type, value, created_at, updated_at) + DB.exec "DELETE FROM site_settings where name = 'uncategorized_category_id'" + DB.exec "INSERT INTO site_settings(name, data_type, value, created_at, updated_at) VALUES ('uncategorized_category_id', 3, #{category_id}, now(), now())" end diff --git a/db/fixtures/006_badges.rb b/db/fixtures/006_badges.rb index 18022ab4513..eae24fec16f 100644 --- a/db/fixtures/006_badges.rb +++ b/db/fixtures/006_badges.rb @@ -31,7 +31,7 @@ BadgeGrouping.seed do |g| end # BUGFIX -Badge.exec_sql <<-SQL.squish +DB.exec <<-SQL.squish UPDATE badges SET badge_grouping_id = -1 WHERE NOT EXISTS ( diff --git a/db/fixtures/500_lounge_category.rb b/db/fixtures/500_lounge_category.rb index 5fa93ddb1e6..2aa42f21cc8 100644 --- a/db/fixtures/500_lounge_category.rb +++ b/db/fixtures/500_lounge_category.rb @@ -36,7 +36,7 @@ unless Rails.env.test? end # Reset topic count because we don't count the description topic - Category.exec_sql "UPDATE categories SET topic_count = 0 WHERE id = #{lounge.id}" + DB.exec "UPDATE categories SET topic_count = 0 WHERE id = #{lounge.id}" end end end diff --git a/db/fixtures/501_meta_category.rb b/db/fixtures/501_meta_category.rb index 38ed96aaea0..38f89856bb9 100644 --- a/db/fixtures/501_meta_category.rb +++ b/db/fixtures/501_meta_category.rb @@ -25,7 +25,7 @@ unless Rails.env.test? end # Reset topic count because we don't count the description topic - Category.exec_sql "UPDATE categories SET topic_count = 0 WHERE id = #{meta.id}" + DB.exec "UPDATE categories SET topic_count = 0 WHERE id = #{meta.id}" end end end diff --git a/db/fixtures/502_staff_category.rb b/db/fixtures/502_staff_category.rb index 54faa6385bf..18b5c54337d 100644 --- a/db/fixtures/502_staff_category.rb +++ b/db/fixtures/502_staff_category.rb @@ -33,7 +33,7 @@ unless Rails.env.test? end # Reset topic count because we don't count the description topic - Category.exec_sql "UPDATE categories SET topic_count = 0 WHERE id = #{staff.id}" + DB.exec "UPDATE categories SET topic_count = 0 WHERE id = #{staff.id}" end end end diff --git a/db/migrate/20120816205538_add_starred_at_to_forum_thread_user.rb b/db/migrate/20120816205538_add_starred_at_to_forum_thread_user.rb index 2fc02d4287b..c2643594d43 100644 --- a/db/migrate/20120816205538_add_starred_at_to_forum_thread_user.rb +++ b/db/migrate/20120816205538_add_starred_at_to_forum_thread_user.rb @@ -1,7 +1,7 @@ class AddStarredAtToForumThreadUser < ActiveRecord::Migration[4.2] def up add_column :forum_thread_users, :starred_at, :datetime - User.exec_sql 'update forum_thread_users f set starred_at = COALESCE(created_at, ?) + DB.exec 'update forum_thread_users f set starred_at = COALESCE(created_at, ?) from ( select f1.forum_thread_id, f1.user_id, t.created_at from forum_thread_users f1 diff --git a/db/migrate/20121123054127_make_post_number_distinct.rb b/db/migrate/20121123054127_make_post_number_distinct.rb index 695ac7392c0..259cdbb9156 100644 --- a/db/migrate/20121123054127_make_post_number_distinct.rb +++ b/db/migrate/20121123054127_make_post_number_distinct.rb @@ -1,7 +1,7 @@ class MakePostNumberDistinct < ActiveRecord::Migration[4.2] def up - Topic.exec_sql('update posts p + DB.exec('update posts p set post_number = calc from ( diff --git a/db/migrate/20140120155706_add_lounge_category.rb b/db/migrate/20140120155706_add_lounge_category.rb index 0dac6e7b291..9e2517d8ddb 100644 --- a/db/migrate/20140120155706_add_lounge_category.rb +++ b/db/migrate/20140120155706_add_lounge_category.rb @@ -3,25 +3,25 @@ class AddLoungeCategory < ActiveRecord::Migration[4.2] return if Rails.env.test? I18n.overrides_disabled do - result = Category.exec_sql "SELECT 1 FROM site_settings where name = 'lounge_category_id'" - if result.count == 0 + result = DB.exec "SELECT 1 FROM site_settings where name = 'lounge_category_id'" + if result == 0 description = I18n.t('vip_category_description') default_name = I18n.t('vip_category_name') - name = if Category.exec_sql("SELECT 1 FROM categories where name = '#{default_name}'").count == 0 + name = if DB.exec("SELECT 1 FROM categories where name = '#{default_name}'") == 0 default_name else "CHANGE_ME" end - result = Category.exec_sql "INSERT INTO categories + result = DB.query_single "INSERT INTO categories (name, color, text_color, created_at, updated_at, user_id, slug, description, read_restricted, position) VALUES (:name, 'EEEEEE', '652D90', now(), now(), -1, '', :description, true, 3) RETURNING id", name: name, description: description - category_id = result[0]["id"].to_i + category_id = result.first.to_i - Category.exec_sql "UPDATE categories SET slug = :slug + DB.exec "UPDATE categories SET slug = :slug WHERE id = :category_id", slug: Slug.for(name, "#{category_id}-category"), category_id: category_id diff --git a/db/migrate/20140122043508_add_meta_category.rb b/db/migrate/20140122043508_add_meta_category.rb index 2b42b81643a..c4ee6b6b6bb 100644 --- a/db/migrate/20140122043508_add_meta_category.rb +++ b/db/migrate/20140122043508_add_meta_category.rb @@ -3,20 +3,20 @@ class AddMetaCategory < ActiveRecord::Migration[4.2] return if Rails.env.test? I18n.overrides_disabled do - result = Category.exec_sql "SELECT 1 FROM site_settings where name = 'meta_category_id'" - if result.count == 0 + result = DB.exec "SELECT 1 FROM site_settings where name = 'meta_category_id'" + if result == 0 description = I18n.t('meta_category_description') name = I18n.t('meta_category_name') - if Category.exec_sql("SELECT 1 FROM categories where name ilike :name", name: name).count == 0 - result = Category.exec_sql "INSERT INTO categories + if DB.exec("SELECT 1 FROM categories where name ilike :name", name: name) == 0 + result = DB.query_single "INSERT INTO categories (name, color, text_color, created_at, updated_at, user_id, slug, description, read_restricted, position) VALUES (:name, '808281', 'FFFFFF', now(), now(), -1, :slug, :description, true, 1) RETURNING id", name: name, slug: '', description: description - category_id = result[0]["id"].to_i + category_id = result.first.to_i - Category.exec_sql "UPDATE categories SET slug=:slug WHERE id=:category_id", + DB.exec "UPDATE categories SET slug=:slug WHERE id=:category_id", slug: Slug.for(name, "#{category_id}-category"), category_id: category_id execute "INSERT INTO site_settings(name, data_type, value, created_at, updated_at) diff --git a/db/migrate/20140227201005_add_staff_category.rb b/db/migrate/20140227201005_add_staff_category.rb index 67a84806016..837daaaef0e 100644 --- a/db/migrate/20140227201005_add_staff_category.rb +++ b/db/migrate/20140227201005_add_staff_category.rb @@ -3,24 +3,24 @@ class AddStaffCategory < ActiveRecord::Migration[4.2] return if Rails.env.test? I18n.overrides_disabled do - result = Category.exec_sql "SELECT 1 FROM site_settings where name = 'staff_category_id'" - if result.count == 0 + result = DB.exec "SELECT 1 FROM site_settings where name = 'staff_category_id'" + if result == 0 description = I18n.t('staff_category_description') name = I18n.t('staff_category_name') - if Category.exec_sql("SELECT 1 FROM categories where name ilike :name", name: name).count == 0 + if DB.exec("SELECT 1 FROM categories where name ilike :name", name: name) == 0 - result = Category.exec_sql "INSERT INTO categories + result = DB.query_single "INSERT INTO categories (name, color, text_color, created_at, updated_at, user_id, slug, description, read_restricted, position) VALUES (:name, '283890', 'FFFFFF', now(), now(), -1, '', :description, true, 2) RETURNING id", name: name, description: description - category_id = result[0]["id"].to_i + category_id = result.first.to_i - Category.exec_sql "UPDATE categories SET slug=:slug WHERE id=:category_id", + DB.exec "UPDATE categories SET slug=:slug WHERE id=:category_id", slug: Slug.for(name, "#{category_id}-category"), category_id: category_id - execute "INSERT INTO site_settings(name, data_type, value, created_at, updated_at) + DB.exec "INSERT INTO site_settings(name, data_type, value, created_at, updated_at) VALUES ('staff_category_id', 3, #{category_id.to_i}, now(), now())" end end diff --git a/db/migrate/20140515220111_init_fixed_category_positions_value.rb b/db/migrate/20140515220111_init_fixed_category_positions_value.rb index f9f454848f7..8b9a545b7ad 100644 --- a/db/migrate/20140515220111_init_fixed_category_positions_value.rb +++ b/db/migrate/20140515220111_init_fixed_category_positions_value.rb @@ -1,10 +1,10 @@ class InitFixedCategoryPositionsValue < ActiveRecord::Migration[4.2] def up # Look at existing categories to determine if positions have been specified - result = Category.exec_sql("SELECT count(*) FROM categories WHERE position IS NOT NULL") + result = DB.query_single("SELECT count(*) FROM categories WHERE position IS NOT NULL") # Greater than 4 because uncategorized, meta, staff, lounge all have positions by default - if result[0]['count'].to_i > 4 + if result.first.to_i > 4 execute "INSERT INTO site_settings (name, data_type, value, created_at, updated_at) VALUES ('fixed_category_positions', 5, 't', now(), now())" end end diff --git a/db/migrate/20140521220115_google_openid_default_has_changed.rb b/db/migrate/20140521220115_google_openid_default_has_changed.rb index d76718c691f..bea872001e6 100644 --- a/db/migrate/20140521220115_google_openid_default_has_changed.rb +++ b/db/migrate/20140521220115_google_openid_default_has_changed.rb @@ -1,17 +1,17 @@ class GoogleOpenidDefaultHasChanged < ActiveRecord::Migration[4.2] def up - users_count_query = User.exec_sql("SELECT count(*) FROM users") - if users_count_query[0]['count'].to_i > 1 + users_count_query = DB.query_single("SELECT count(*) FROM users") + if users_count_query.first.to_i > 1 # This is an existing site. - result = User.exec_sql("SELECT count(*) FROM site_settings WHERE name = 'enable_google_logins'") - if result[0]['count'].to_i == 0 + result = DB.query_single("SELECT count(*) FROM site_settings WHERE name = 'enable_google_logins'") + if result.first.to_i == 0 # The old default was true, so add a row to keep it that way. execute "INSERT INTO site_settings (name, data_type, value, created_at, updated_at) VALUES ('enable_google_logins', 5, 't', now(), now())" end # Don't enable the new Google setting on an existing site. - result = User.exec_sql("SELECT count(*) FROM site_settings WHERE name = 'enable_google_oauth2_logins'") - if result[0]['count'].to_i == 0 + result = DB.query_single("SELECT count(*) FROM site_settings WHERE name = 'enable_google_oauth2_logins'") + if result.first.to_i == 0 execute "INSERT INTO site_settings (name, data_type, value, created_at, updated_at) VALUES ('enable_google_oauth2_logins', 5, 'f', now(), now())" end end diff --git a/db/migrate/20140604145431_disable_external_auths_by_default.rb b/db/migrate/20140604145431_disable_external_auths_by_default.rb index 8f47ba505ec..2cd19018df3 100644 --- a/db/migrate/20140604145431_disable_external_auths_by_default.rb +++ b/db/migrate/20140604145431_disable_external_auths_by_default.rb @@ -1,15 +1,15 @@ class DisableExternalAuthsByDefault < ActiveRecord::Migration[4.2] def enable_setting_if_default(name) - result = User.exec_sql("SELECT count(*) count FROM site_settings WHERE name = '#{name}'") - if result[0]['count'].to_i == 0 + result = DB.query_single("SELECT count(*) count FROM site_settings WHERE name = '#{name}'") + if result.first.to_i == 0 execute "INSERT INTO site_settings (name, data_type, value, created_at, updated_at) VALUES ('#{name}', 5, 't', now(), now())" end end def up - users_count_query = User.exec_sql("SELECT count(*) FROM users") - if users_count_query[0]['count'].to_i > 1 + users_count_query = DB.query_single("SELECT count(*) FROM users") + if users_count_query.first.to_i > 1 # existing site, so keep settings as they are enable_setting_if_default 'enable_yahoo_logins' enable_setting_if_default 'enable_google_oauth2_logins' diff --git a/db/migrate/20140711193923_remove_email_in_address_setting.rb b/db/migrate/20140711193923_remove_email_in_address_setting.rb index a82dbe4dd08..a14d44c7635 100644 --- a/db/migrate/20140711193923_remove_email_in_address_setting.rb +++ b/db/migrate/20140711193923_remove_email_in_address_setting.rb @@ -1,16 +1,16 @@ class RemoveEmailInAddressSetting < ActiveRecord::Migration[4.2] def up - uncat_id = ActiveRecord::Base.exec_sql("SELECT value FROM site_settings WHERE name = 'uncategorized_category_id'").first - cat_id_r = ActiveRecord::Base.exec_sql("SELECT value FROM site_settings WHERE name = 'email_in_category'").first - email_r = ActiveRecord::Base.exec_sql("SELECT value FROM site_settings WHERE name = 'email_in_address'").first + uncat_id = DB.query_single("SELECT value FROM site_settings WHERE name = 'uncategorized_category_id'").first + cat_id_r = DB.query_single("SELECT value FROM site_settings WHERE name = 'email_in_category'").first + email_r = DB.query_single("SELECT value FROM site_settings WHERE name = 'email_in_address'").first if email_r category_id = uncat_id["value"].to_i category_id = cat_id_r["value"].to_i if cat_id_r email = email_r["value"] - ActiveRecord::Base.exec_sql("UPDATE categories SET email_in = ? WHERE id = ?", email, category_id) + DB.exec("UPDATE categories SET email_in = ? WHERE id = ?", email, category_id) end - ActiveRecord::Base.exec_sql("DELETE FROM site_settings WHERE name = 'email_in_category' OR name = 'email_in_address'") + DB.exec("DELETE FROM site_settings WHERE name = 'email_in_category' OR name = 'email_in_address'") end def down diff --git a/db/migrate/20141216112341_resolve_duplicate_group_names.rb b/db/migrate/20141216112341_resolve_duplicate_group_names.rb index c13b1f7b808..96cb718f8d8 100644 --- a/db/migrate/20141216112341_resolve_duplicate_group_names.rb +++ b/db/migrate/20141216112341_resolve_duplicate_group_names.rb @@ -1,14 +1,14 @@ class ResolveDuplicateGroupNames < ActiveRecord::Migration[4.2] def up - results = Group.exec_sql 'SELECT id FROM groups + results = DB.query_single 'SELECT id FROM groups WHERE name ILIKE (SELECT lower(name) FROM groups GROUP BY lower(name) HAVING count(*) > 1);' - groups = Group.where id: results.map { |r| r['id'] } + groups = Group.where id: results groups.group_by { |g| g.name.downcase }.each do |key, value| value.each_with_index do |dup, index| dup.update! name: "#{dup.name[0..18]}_#{index + 1}" if index > 0 diff --git a/db/migrate/20161025083648_fix_category_logo_and_background_urls.rb b/db/migrate/20161025083648_fix_category_logo_and_background_urls.rb index a3fe7b251b2..ea96cf0ee13 100644 --- a/db/migrate/20161025083648_fix_category_logo_and_background_urls.rb +++ b/db/migrate/20161025083648_fix_category_logo_and_background_urls.rb @@ -2,7 +2,7 @@ class FixCategoryLogoAndBackgroundUrls < ActiveRecord::Migration[4.2] def up return true if Discourse.asset_host.blank? - Category.exec_sql <<-SQL + DB.exec <<-SQL UPDATE categories SET logo_url = replace(logo_url, '#{Discourse.asset_host}', '') , background_url = replace(background_url, '#{Discourse.asset_host}', '') diff --git a/db/migrate/20170215151505_add_seen_at_to_user_auth_token.rb b/db/migrate/20170215151505_add_seen_at_to_user_auth_token.rb index 50441273217..4ed90b62230 100644 --- a/db/migrate/20170215151505_add_seen_at_to_user_auth_token.rb +++ b/db/migrate/20170215151505_add_seen_at_to_user_auth_token.rb @@ -1,7 +1,7 @@ class AddSeenAtToUserAuthToken < ActiveRecord::Migration[4.2] def up add_column :user_auth_tokens, :seen_at, :datetime - ActiveRecord::Base.exec_sql "UPDATE user_auth_tokens SET seen_at = :now WHERE auth_token_seen", now: Time.zone.now + DB.exec "UPDATE user_auth_tokens SET seen_at = :now WHERE auth_token_seen", now: Time.zone.now end def down diff --git a/db/migrate/20170728012754_split_public_in_groups.rb b/db/migrate/20170728012754_split_public_in_groups.rb index febb2f9779f..688baaceff1 100644 --- a/db/migrate/20170728012754_split_public_in_groups.rb +++ b/db/migrate/20170728012754_split_public_in_groups.rb @@ -3,7 +3,7 @@ class SplitPublicInGroups < ActiveRecord::Migration[4.2] add_column :groups, :public_exit, :boolean, default: false, null: false add_column :groups, :public_admission, :boolean, default: false, null: false - ActiveRecord::Base.exec_sql <<~SQL + DB.exec <<~SQL UPDATE groups SET public_exit = true, public_admission = true WHERE public = true diff --git a/db/migrate/20180308071922_drop_raise_read_only_function.rb b/db/migrate/20180308071922_drop_raise_read_only_function.rb index a90a2561c9f..d756aa7e14c 100644 --- a/db/migrate/20180308071922_drop_raise_read_only_function.rb +++ b/db/migrate/20180308071922_drop_raise_read_only_function.rb @@ -1,6 +1,6 @@ class DropRaiseReadOnlyFunction < ActiveRecord::Migration[5.1] def up - ActiveRecord::Base.exec_sql( + DB.exec( "DROP FUNCTION IF EXISTS raise_read_only() CASCADE;" ) end diff --git a/lib/backup_restore/backup_restore.rb b/lib/backup_restore/backup_restore.rb index a0a217556e3..0591b31fe11 100644 --- a/lib/backup_restore/backup_restore.rb +++ b/lib/backup_restore/backup_restore.rb @@ -76,7 +76,7 @@ module BackupRestore end def self.move_tables_between_schemas(source, destination) - User.exec_sql(move_tables_between_schemas_sql(source, destination)) + DB.exec(move_tables_between_schemas_sql(source, destination)) end def self.move_tables_between_schemas_sql(source, destination) @@ -196,7 +196,7 @@ module BackupRestore end def self.backup_tables_count - User.exec_sql("SELECT COUNT(*) AS count FROM information_schema.tables WHERE table_schema = 'backup'")[0]['count'].to_i + DB.query_single("SELECT COUNT(*) AS count FROM information_schema.tables WHERE table_schema = 'backup'").first.to_i end end diff --git a/lib/backup_restore/restorer.rb b/lib/backup_restore/restorer.rb index d2d46239be4..fefe6c45774 100644 --- a/lib/backup_restore/restorer.rb +++ b/lib/backup_restore/restorer.rb @@ -379,14 +379,14 @@ module BackupRestore @db_was_changed = true - User.exec_sql(sql) + DB.exec(sql) end def migrate_database log "Migrating the database..." Discourse::Application.load_tasks ENV["VERSION"] = @current_version.to_s - User.exec_sql("SET search_path = public, pg_catalog;") + DB.exec("SET search_path = public, pg_catalog;") Rake::Task["db:migrate"].invoke end diff --git a/lib/comment_migration.rb b/lib/comment_migration.rb index 79c1576dd7a..bd00d58c37d 100644 --- a/lib/comment_migration.rb +++ b/lib/comment_migration.rb @@ -13,10 +13,10 @@ class CommentMigration < ActiveRecord::Migration[4.2] comment = column[1] if column_name == :_table - ActiveRecord::Base.exec_sql "COMMENT ON TABLE #{table_name} IS ?", comment + DB.exec "COMMENT ON TABLE #{table_name} IS ?", comment puts " COMMENT ON TABLE #{table_name}" else - ActiveRecord::Base.exec_sql "COMMENT ON COLUMN #{table_name}.#{column_name} IS ?", comment + DB.exec "COMMENT ON COLUMN #{table_name}.#{column_name} IS ?", comment puts " COMMENT ON COLUMN #{table_name}.#{column_name}" end end @@ -35,10 +35,10 @@ class CommentMigration < ActiveRecord::Migration[4.2] comment = column[1] if column_name == :_table - ActiveRecord::Base.exec_sql "COMMENT ON TABLE #{table_name} IS ?", comment + DB.exec "COMMENT ON TABLE #{table_name} IS ?", comment puts " COMMENT ON TABLE #{table_name}" else - ActiveRecord::Base.exec_sql "COMMENT ON COLUMN #{table_name}.#{column_name} IS ?", comment + DB.exec "COMMENT ON COLUMN #{table_name}.#{column_name} IS ?", comment puts " COMMENT ON COLUMN #{table_name}.#{column_name}" end end diff --git a/lib/cooked_post_processor.rb b/lib/cooked_post_processor.rb index 12e2d35e056..93866749e54 100644 --- a/lib/cooked_post_processor.rb +++ b/lib/cooked_post_processor.rb @@ -73,7 +73,7 @@ class CookedPostProcessor PostUpload.transaction do PostUpload.where(post_id: @post.id).delete_all if upload_ids.size > 0 - PostUpload.exec_sql("INSERT INTO post_uploads (post_id, upload_id) VALUES #{values}") + DB.exec("INSERT INTO post_uploads (post_id, upload_id) VALUES #{values}") end end end diff --git a/lib/migration/base_dropper.rb b/lib/migration/base_dropper.rb index f36963d99ec..d8e2f03d932 100644 --- a/lib/migration/base_dropper.rb +++ b/lib/migration/base_dropper.rb @@ -48,7 +48,7 @@ module Migration "Discourse: #{column_name} in #{table_name} is readonly" : "Discourse: #{table_name} is read only" - ActiveRecord::Base.exec_sql <<~SQL + DB.exec <<~SQL CREATE OR REPLACE FUNCTION #{readonly_function_name(table_name, column_name)} RETURNS trigger AS $rcr$ BEGIN RAISE EXCEPTION '#{message}'; diff --git a/lib/migration/column_dropper.rb b/lib/migration/column_dropper.rb index d7176505d15..a594105622b 100644 --- a/lib/migration/column_dropper.rb +++ b/lib/migration/column_dropper.rb @@ -12,7 +12,7 @@ module Migration def self.mark_readonly(table_name, column_name) create_readonly_function(table_name, column_name) - ActiveRecord::Base.exec_sql <<~SQL + DB.exec <<~SQL CREATE TRIGGER #{readonly_trigger_name(table_name, column_name)} BEFORE INSERT OR UPDATE OF #{column_name} ON #{table_name} @@ -51,13 +51,13 @@ module Migration def execute_drop! @columns.each do |column| - ActiveRecord::Base.exec_sql <<~SQL + DB.exec <<~SQL DROP TRIGGER IF EXISTS #{BaseDropper.readonly_trigger_name(@table, column)} ON #{@table}; DROP FUNCTION IF EXISTS #{BaseDropper.readonly_function_name(@table, column)} CASCADE; SQL # safe cause it is protected on method entry, can not be passed in params - ActiveRecord::Base.exec_sql("ALTER TABLE #{@table} DROP COLUMN IF EXISTS #{column}") + DB.exec("ALTER TABLE #{@table} DROP COLUMN IF EXISTS #{column}") end end end diff --git a/lib/migration/table_dropper.rb b/lib/migration/table_dropper.rb index 78a9c7172f5..c95640b78dc 100644 --- a/lib/migration/table_dropper.rb +++ b/lib/migration/table_dropper.rb @@ -18,7 +18,7 @@ module Migration def self.read_only_table(table_name) create_readonly_function(table_name) - ActiveRecord::Base.exec_sql <<~SQL + DB.exec <<~SQL CREATE TRIGGER #{readonly_trigger_name(table_name)} BEFORE INSERT OR UPDATE OR DELETE OR TRUNCATE ON #{table_name} @@ -37,7 +37,7 @@ module Migration end def droppable? - builder = SqlBuilder.new(<<~SQL) + builder = DB.build(<<~SQL) SELECT 1 FROM INFORMATION_SCHEMA.TABLES /*where*/ @@ -52,7 +52,7 @@ module Migration .exec(old_name: @old_name, new_name: @new_name, delay: "#{@delay} seconds", - after_migration: @after_migration).to_a.length > 0 + after_migration: @after_migration) > 0 end def table_exists(table_name_placeholder) @@ -67,9 +67,9 @@ module Migration end def execute_drop! - ActiveRecord::Base.exec_sql("DROP TABLE IF EXISTS #{@old_name}") + DB.exec("DROP TABLE IF EXISTS #{@old_name}") - ActiveRecord::Base.exec_sql <<~SQL + DB.exec <<~SQL DROP FUNCTION IF EXISTS #{BaseDropper.readonly_function_name(@old_name)} CASCADE; SQL end diff --git a/lib/mini_sql_multisite_connection.rb b/lib/mini_sql_multisite_connection.rb new file mode 100644 index 00000000000..d2645a6af84 --- /dev/null +++ b/lib/mini_sql_multisite_connection.rb @@ -0,0 +1,38 @@ +class MiniSqlMultisiteConnection < MiniSql::Connection + + class CustomBuilder < MiniSql::Builder + + def initialize(connection, sql) + super + 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 + end + + class ParamEncoder + def encode(*sql_array) + # use active record to avoid any discrepencies + ActiveRecord::Base.send(:sanitize_sql_array, sql_array) + end + end + + def self.instance + new(nil, param_encoder: ParamEncoder.new, type_map: self.type_map(ActiveRecord::Base.connection.raw_connection)) + end + # we need a tiny adapter here so we always run against the + # correct multisite connection + def raw_connection + ActiveRecord::Base.connection.raw_connection + end + + def build(sql) + CustomBuilder.new(self, sql) + end +end diff --git a/lib/post_revisor.rb b/lib/post_revisor.rb index d74e50dd95b..b406351256c 100644 --- a/lib/post_revisor.rb +++ b/lib/post_revisor.rb @@ -566,7 +566,7 @@ class PostRevisor end def update_topic_word_counts - Topic.exec_sql("UPDATE topics + DB.exec("UPDATE topics SET word_count = ( SELECT SUM(COALESCE(posts.word_count, 0)) FROM posts diff --git a/lib/search.rb b/lib/search.rb index 054f7e5fd05..f9cfdbd1a49 100644 --- a/lib/search.rb +++ b/lib/search.rb @@ -790,9 +790,11 @@ class Search def self.ts_query(term: , ts_config: nil, joiner: "&", weight_filter: nil) - data = Post.exec_sql("SELECT TO_TSVECTOR(:config, :term)", - config: 'simple', - term: term).values[0][0] + data = DB.query_single( + "SELECT TO_TSVECTOR(:config, :term)", + config: 'simple', + term: term + ).first ts_config = ActiveRecord::Base.connection.quote(ts_config) if ts_config all_terms = data.scan(/'([^']+)'\:\d+/).flatten diff --git a/lib/tasks/db.rake b/lib/tasks/db.rake index e7782f1a859..1d1c5fa0eda 100644 --- a/lib/tasks/db.rake +++ b/lib/tasks/db.rake @@ -96,7 +96,7 @@ task 'db:stats' => 'environment' do SQL puts - print_table(Post.exec_sql(sql).to_a) + print_table(DB.query_hash(sql)) end desc 'Rebuild indexes' @@ -108,16 +108,14 @@ task 'db:rebuild_indexes' => 'environment' do Discourse.enable_readonly_mode backup_schema = Jobs::Importer::BACKUP_SCHEMA - table_names = User.exec_sql("select table_name from information_schema.tables where table_schema = 'public'").map do |row| - row['table_name'] - end + table_names = DB.query_single("select table_name from information_schema.tables where table_schema = 'public'") begin # Move all tables to the backup schema: - User.exec_sql("DROP SCHEMA IF EXISTS #{backup_schema} CASCADE") - User.exec_sql("CREATE SCHEMA #{backup_schema}") + DB.exec("DROP SCHEMA IF EXISTS #{backup_schema} CASCADE") + DB.exec("CREATE SCHEMA #{backup_schema}") table_names.each do |table_name| - User.exec_sql("ALTER TABLE public.#{table_name} SET SCHEMA #{backup_schema}") + DB.exec("ALTER TABLE public.#{table_name} SET SCHEMA #{backup_schema}") end # Create a new empty db @@ -126,25 +124,25 @@ task 'db:rebuild_indexes' => 'environment' do # Fetch index definitions from the new db index_definitions = {} table_names.each do |table_name| - index_definitions[table_name] = User.exec_sql("SELECT indexdef FROM pg_indexes WHERE tablename = '#{table_name}' and schemaname = 'public';").map { |x| x['indexdef'] } + index_definitions[table_name] = DB.query_single("SELECT indexdef FROM pg_indexes WHERE tablename = '#{table_name}' and schemaname = 'public';") end # Drop the new tables table_names.each do |table_name| - User.exec_sql("DROP TABLE public.#{table_name}") + DB.exec("DROP TABLE public.#{table_name}") end # Move the old tables back to the public schema table_names.each do |table_name| - User.exec_sql("ALTER TABLE #{backup_schema}.#{table_name} SET SCHEMA public") + DB.exec("ALTER TABLE #{backup_schema}.#{table_name} SET SCHEMA public") end # Drop their indexes - index_names = User.exec_sql("SELECT indexname FROM pg_indexes WHERE schemaname = 'public' AND tablename IN ('#{table_names.join("', '")}')").map { |x| x['indexname'] } + index_names = DB.query_single("SELECT indexname FROM pg_indexes WHERE schemaname = 'public' AND tablename IN ('#{table_names.join("', '")}')") index_names.each do |index_name| begin puts index_name - User.exec_sql("DROP INDEX public.#{index_name}") + DB.exec("DROP INDEX public.#{index_name}") rescue ActiveRecord::StatementInvalid # It's this: # PG::Error: ERROR: cannot drop index category_users_pkey because constraint category_users_pkey on table category_users requires it @@ -156,7 +154,7 @@ task 'db:rebuild_indexes' => 'environment' do table_names.each do |table_name| index_definitions[table_name].each do |index_def| begin - User.exec_sql(index_def) + DB.exec(index_def) rescue ActiveRecord::StatementInvalid # Trying to recreate a primary key end diff --git a/lib/tasks/import.rake b/lib/tasks/import.rake index ad291fd96a7..7d83eb0115c 100644 --- a/lib/tasks/import.rake +++ b/lib/tasks/import.rake @@ -29,7 +29,7 @@ MS_SPEND_CREATING_POST ||= 5000 def insert_post_timings log "Inserting post timings..." - exec_sql <<-SQL + DB.exec <<-SQL INSERT INTO post_timings (topic_id, post_number, user_id, msecs) SELECT topic_id, post_number, user_id, #{MS_SPEND_CREATING_POST} FROM posts @@ -41,7 +41,7 @@ end def insert_post_replies log "Inserting post replies..." - exec_sql <<-SQL + DB.exec <<-SQL INSERT INTO post_replies (post_id, reply_id, created_at, updated_at) SELECT p2.id, p.id, p.created_at, p.created_at FROM posts p @@ -53,7 +53,7 @@ end def insert_topic_users log "Inserting topic users..." - exec_sql <<-SQL + DB.exec <<-SQL INSERT INTO topic_users (user_id, topic_id, posted, last_read_post_number, highest_seen_post_number, first_visited_at, last_visited_at, total_msecs_viewed) SELECT user_id, topic_id, 't' , MAX(post_number), MAX(post_number), MIN(created_at), MAX(created_at), COUNT(id) * #{MS_SPEND_CREATING_POST} FROM posts @@ -66,7 +66,7 @@ end def insert_topic_views log "Inserting topic views..." - exec_sql <<-SQL + DB.exec <<-SQL WITH X AS ( SELECT topic_id, user_id, DATE(p.created_at) posted_at FROM posts p @@ -86,7 +86,7 @@ end def insert_user_actions log "Inserting user actions for NEW_TOPIC = 4..." - exec_sql <<-SQL + DB.exec <<-SQL INSERT INTO user_actions (action_type, user_id, target_topic_id, target_post_id, acting_user_id, created_at, updated_at) SELECT 4, p.user_id, topic_id, p.id, p.user_id, p.created_at, p.created_at FROM posts p @@ -100,7 +100,7 @@ def insert_user_actions log "Inserting user actions for REPLY = 5..." - exec_sql <<-SQL + DB.exec <<-SQL INSERT INTO user_actions (action_type, user_id, target_topic_id, target_post_id, acting_user_id, created_at, updated_at) SELECT 5, p.user_id, topic_id, p.id, p.user_id, p.created_at, p.created_at FROM posts p @@ -114,7 +114,7 @@ def insert_user_actions log "Inserting user actions for RESPONSE = 6..." - exec_sql <<-SQL + DB.exec <<-SQL INSERT INTO user_actions (action_type, user_id, target_topic_id, target_post_id, acting_user_id, created_at, updated_at) SELECT 6, p.user_id, p.topic_id, p.id, p2.user_id, p.created_at, p.created_at FROM posts p @@ -137,7 +137,7 @@ end def insert_user_options log "Inserting user options..." - exec_sql <<-SQL + DB.exec <<-SQL INSERT INTO user_options ( user_id, email_always, @@ -189,7 +189,7 @@ end def insert_user_stats log "Inserting user stats..." - exec_sql <<-SQL + DB.exec <<-SQL INSERT INTO user_stats (user_id, new_since) SELECT id, created_at FROM users @@ -200,7 +200,7 @@ end def insert_user_visits log "Inserting user visits..." - exec_sql <<-SQL + DB.exec <<-SQL INSERT INTO user_visits (user_id, visited_at, posts_read) SELECT user_id, DATE(created_at), COUNT(*) FROM posts @@ -213,7 +213,7 @@ end def insert_draft_sequences log "Inserting draft sequences..." - exec_sql <<-SQL + DB.exec <<-SQL INSERT INTO draft_sequences (user_id, draft_key, sequence) SELECT user_id, CONCAT('#{Draft::EXISTING_TOPIC}', id), 1 FROM topics @@ -226,7 +226,7 @@ end def update_user_stats log "Updating user stats..." - exec_sql <<-SQL + DB.exec <<-SQL WITH X AS ( SELECT p.user_id , COUNT(p.id) posts @@ -283,7 +283,7 @@ end def update_posts log "Updating posts..." - exec_sql <<-SQL + DB.exec <<-SQL WITH Y AS ( SELECT post_id, COUNT(*) replies FROM post_replies GROUP BY post_id ) @@ -310,7 +310,7 @@ end def update_topics log "Updating topics..." - exec_sql <<-SQL + DB.exec <<-SQL WITH X AS ( SELECT topic_id , COUNT(*) posts @@ -350,7 +350,7 @@ end def update_categories log "Updating categories..." - exec_sql <<-SQL + DB.exec <<-SQL WITH X AS ( SELECT category_id , MAX(p.id) post_id @@ -382,7 +382,7 @@ end def update_users log "Updating users..." - exec_sql <<-SQL + DB.exec <<-SQL WITH X AS ( SELECT user_id , MIN(created_at) min_created_at @@ -406,7 +406,7 @@ end def update_groups log "Updating groups..." - exec_sql <<-SQL + DB.exec <<-SQL WITH X AS ( SELECT group_id, COUNT(*) count FROM group_users @@ -428,12 +428,6 @@ def log(message) puts "[#{DateTime.now.strftime("%Y-%m-%d %H:%M:%S")}] #{message}" end -def exec_sql(sql) - ActiveRecord::Base.transaction do - ActiveRecord::Base.exec_sql(sql) - end -end - task "import:create_phpbb_permalinks" => :environment do log 'Creating Permalinks...' @@ -477,7 +471,6 @@ task "import:remap_old_phpbb_permalinks" => :environment do # skip end end - i log "Done! #{i} posts remapped." end diff --git a/lib/tasks/posts.rake b/lib/tasks/posts.rake index 3c17bc1e3bd..9909bb3c01e 100644 --- a/lib/tasks/posts.rake +++ b/lib/tasks/posts.rake @@ -304,7 +304,7 @@ task 'posts:reorder_posts', [:topic_id] => [:environment] do |_, args| builder.where("topic_id = :topic_id") if args[:topic_id] builder.exec(topic_id: args[:topic_id]) - Notification.exec_sql(<<~SQL) + DB.exec(<<~SQL) UPDATE notifications AS x SET post_number = p.sort_order FROM posts AS p @@ -313,7 +313,7 @@ task 'posts:reorder_posts', [:topic_id] => [:environment] do |_, args| p.post_number < 0 SQL - PostTiming.exec_sql(<<~SQL) + DB.exec(<<~SQL) UPDATE post_timings AS x SET post_number = x.post_number * -1 FROM posts AS p @@ -329,7 +329,7 @@ task 'posts:reorder_posts', [:topic_id] => [:environment] do |_, args| p.post_number < 0; SQL - Post.exec_sql(<<~SQL) + DB.exec(<<~SQL) UPDATE posts AS x SET reply_to_post_number = p.sort_order FROM posts AS p @@ -338,7 +338,7 @@ task 'posts:reorder_posts', [:topic_id] => [:environment] do |_, args| p.post_number < 0; SQL - TopicUser.exec_sql(<<~SQL) + DB.exec(<<~SQL) UPDATE topic_users AS x SET last_read_post_number = p.sort_order FROM posts AS p @@ -362,7 +362,7 @@ task 'posts:reorder_posts', [:topic_id] => [:environment] do |_, args| SQL # finally update the post_number - Post.exec_sql(<<~SQL) + DB.exec(<<~SQL) UPDATE posts SET post_number = sort_order WHERE post_number < 0 diff --git a/lib/tasks/uploads.rake b/lib/tasks/uploads.rake index d69f3078a73..6e5b7fcb3e2 100644 --- a/lib/tasks/uploads.rake +++ b/lib/tasks/uploads.rake @@ -676,7 +676,7 @@ task "uploads:analyze", [:cache_path, :limit] => :environment do |_, args| printf "%-25s | %-25s | %-25s | %-25s\n", 'username', 'total size of uploads', 'number of uploads', 'number of optimized images' puts "-" * 110 - User.exec_sql(sql).values.each do |username, num_of_uploads, total_size_of_uploads, num_of_optimized_images| + DB.query_single(sql).each do |username, num_of_uploads, total_size_of_uploads, num_of_optimized_images| printf "%-25s | %-25s | %-25s | %-25s\n", username, helper.number_to_human_size(total_size_of_uploads), num_of_uploads, num_of_optimized_images end diff --git a/lib/topic_view.rb b/lib/topic_view.rb index d2bf87de10b..45e7f2bc3df 100644 --- a/lib/topic_view.rb +++ b/lib/topic_view.rb @@ -285,14 +285,14 @@ class TopicView sql = <<~SQL SELECT user_id, count(*) AS count_all FROM posts - WHERE id IN (:post_ids) + WHERE id in (:post_ids) AND user_id IS NOT NULL GROUP BY user_id ORDER BY count_all DESC LIMIT #{MAX_PARTICIPANTS} SQL - Hash[Post.exec_sql(sql, post_ids: post_ids).values] + Hash[*DB.query_single(sql, post_ids: post_ids)] end end @@ -306,7 +306,7 @@ class TopicView WHERE id IN (:post_ids) AND user_id IS NOT NULL SQL - Post.exec_sql(sql, post_ids: unfiltered_post_ids).getvalue(0, 0).to_i + DB.query_single(sql, post_ids: unfiltered_post_ids).first.to_i else participants.size end diff --git a/lib/topics_bulk_action.rb b/lib/topics_bulk_action.rb index b0b5e91b7a9..ce801f1e071 100644 --- a/lib/topics_bulk_action.rb +++ b/lib/topics_bulk_action.rb @@ -72,7 +72,7 @@ class TopicsBulkAction WHERE t.id = tu.topic_id AND tu.user_id = :user_id AND t.id IN (:topic_ids) " - Topic.exec_sql(sql, user_id: @user.id, topic_ids: @topic_ids) + DB.exec(sql, user_id: @user.id, topic_ids: @topic_ids) @changed_ids.concat @topic_ids end diff --git a/script/import_scripts/base.rb b/script/import_scripts/base.rb index 78c04ea6b1a..05d66b0d1d8 100644 --- a/script/import_scripts/base.rb +++ b/script/import_scripts/base.rb @@ -626,7 +626,7 @@ class ImportScripts::Base def update_topic_status puts "", "Updating topic status" - Topic.exec_sql(<<~SQL) + DB.exec(<<~SQL) UPDATE topics AS t SET closed = TRUE WHERE EXISTS( @@ -636,7 +636,7 @@ class ImportScripts::Base ) SQL - Topic.exec_sql(<<~SQL) + DB.exec(<<~SQL) UPDATE topics AS t SET archived = TRUE WHERE EXISTS( @@ -646,7 +646,7 @@ class ImportScripts::Base ) SQL - TopicCustomField.exec_sql(<<~SQL) + DB.exec(<<~SQL) DELETE FROM topic_custom_fields WHERE name IN ('import_closed', 'import_archived') SQL @@ -654,7 +654,7 @@ class ImportScripts::Base def update_bumped_at puts "", "Updating bumped_at on topics" - Post.exec_sql("update topics t set bumped_at = COALESCE((select max(created_at) from posts where topic_id = t.id and post_type = #{Post.types[:regular]}), bumped_at)") + DB.exec("update topics t set bumped_at = COALESCE((select max(created_at) from posts where topic_id = t.id and post_type = #{Post.types[:regular]}), bumped_at)") end def update_last_posted_at @@ -674,7 +674,7 @@ class ImportScripts::Base AND users.last_posted_at <> lpa.last_posted_at SQL - User.exec_sql(sql) + DB.exec(sql) end def update_user_stats @@ -707,7 +707,7 @@ class ImportScripts::Base AND user_stats.first_post_created_at <> sub.first_post_created_at SQL - User.exec_sql(sql) + DB.exec(sql) puts "", "Updating user post_count..." @@ -725,7 +725,7 @@ class ImportScripts::Base AND user_stats.post_count <> sub.post_count SQL - User.exec_sql(sql) + DB.exec(sql) puts "", "Updating user topic_count..." @@ -743,15 +743,15 @@ class ImportScripts::Base AND user_stats.topic_count <> sub.topic_count SQL - User.exec_sql(sql) + DB.exec(sql) end # scripts that are able to import last_seen_at from the source data should override this method def update_last_seen_at puts "", "Updating last seen at on users" - User.exec_sql("UPDATE users SET last_seen_at = created_at WHERE last_seen_at IS NULL") - User.exec_sql("UPDATE users SET last_seen_at = last_posted_at WHERE last_posted_at IS NOT NULL") + DB.exec("UPDATE users SET last_seen_at = created_at WHERE last_seen_at IS NULL") + DB.exec("UPDATE users SET last_seen_at = last_posted_at WHERE last_posted_at IS NOT NULL") end def update_feature_topic_users diff --git a/script/import_scripts/ipboard3.rb b/script/import_scripts/ipboard3.rb index 5a9812f85d5..53d808aa693 100644 --- a/script/import_scripts/ipboard3.rb +++ b/script/import_scripts/ipboard3.rb @@ -267,7 +267,7 @@ class ImportScripts::IPBoard3 < ImportScripts::Base WHERE id IN (SELECT topic_id FROM closed_topic_ids) SQL - Topic.exec_sql(sql, @closed_topic_ids) + DB.exec(sql, @closed_topic_ids) end def import_personal_topics diff --git a/script/import_scripts/jive_api.rb b/script/import_scripts/jive_api.rb index f8c3734a351..923904aef83 100644 --- a/script/import_scripts/jive_api.rb +++ b/script/import_scripts/jive_api.rb @@ -325,7 +325,7 @@ class ImportScripts::JiveApi < ImportScripts::Base def mark_topics_as_solved puts "", "Marking topics as solved..." - PostAction.exec_sql <<-SQL + DB.exec <<~SQL INSERT INTO topic_custom_fields (name, value, topic_id, created_at, updated_at) SELECT 'accepted_answer_post_id', pcf.post_id, p.topic_id, p.created_at, p.created_at FROM post_custom_fields pcf diff --git a/script/import_scripts/lithium.rb b/script/import_scripts/lithium.rb index a778f0831f2..d83fe105e37 100644 --- a/script/import_scripts/lithium.rb +++ b/script/import_scripts/lithium.rb @@ -535,7 +535,7 @@ class ImportScripts::Lithium < ImportScripts::Base end puts "loading data into temp table" - PostAction.exec_sql("create temp table like_data(user_id int, post_id int, created_at timestamp without time zone)") + DB.exec("create temp table like_data(user_id int, post_id int, created_at timestamp without time zone)") PostAction.transaction do results.each do |result| @@ -544,17 +544,17 @@ class ImportScripts::Lithium < ImportScripts::Base next unless result["user_id"] && result["post_id"] - PostAction.exec_sql("INSERT INTO like_data VALUES (:user_id,:post_id,:created_at)", - user_id: result["user_id"], - post_id: result["post_id"], - created_at: result["created_at"] - ) + DB.exec("INSERT INTO like_data VALUES (:user_id,:post_id,:created_at)", + user_id: result["user_id"], + post_id: result["post_id"], + created_at: result["created_at"] + ) end end puts "creating missing post actions" - PostAction.exec_sql <<-SQL + DB.exec <<~SQL INSERT INTO post_actions (post_id, user_id, post_action_type_id, created_at, updated_at) SELECT l.post_id, l.user_id, 2, l.created_at, l.created_at FROM like_data l @@ -563,7 +563,7 @@ class ImportScripts::Lithium < ImportScripts::Base SQL puts "creating missing user actions" - UserAction.exec_sql <<-SQL + DB.exec <<~SQL INSERT INTO user_actions (user_id, action_type, target_topic_id, target_post_id, acting_user_id, created_at, updated_at) SELECT pa.user_id, 1, p.topic_id, p.id, pa.user_id, pa.created_at, pa.created_at FROM post_actions pa @@ -574,7 +574,7 @@ class ImportScripts::Lithium < ImportScripts::Base SQL # reverse action - UserAction.exec_sql <<-SQL + DB.exec <<~SQL INSERT INTO user_actions (user_id, action_type, target_topic_id, target_post_id, acting_user_id, created_at, updated_at) SELECT p.user_id, 2, p.topic_id, p.id, pa.user_id, pa.created_at, pa.created_at FROM post_actions pa @@ -586,7 +586,7 @@ class ImportScripts::Lithium < ImportScripts::Base SQL puts "updating like counts on posts" - Post.exec_sql <<-SQL + DB.exec <<~SQL UPDATE posts SET like_count = coalesce(cnt,0) FROM ( SELECT post_id, count(*) cnt @@ -600,7 +600,7 @@ class ImportScripts::Lithium < ImportScripts::Base puts "updating like counts on topics" - Post.exec_sql <<-SQL + DB.exec <<-SQL UPDATE topics SET like_count = coalesce(cnt,0) FROM ( SELECT topic_id, sum(like_count) cnt @@ -627,7 +627,7 @@ class ImportScripts::Lithium < ImportScripts::Base end puts "loading data into temp table" - PostAction.exec_sql("create temp table accepted_data(post_id int primary key)") + DB.exec("create temp table accepted_data(post_id int primary key)") PostAction.transaction do results.each do |result| @@ -635,7 +635,7 @@ class ImportScripts::Lithium < ImportScripts::Base next unless result["post_id"] - PostAction.exec_sql("INSERT INTO accepted_data VALUES (:post_id)", + DB.exec("INSERT INTO accepted_data VALUES (:post_id)", post_id: result["post_id"] ) @@ -643,7 +643,7 @@ class ImportScripts::Lithium < ImportScripts::Base end puts "deleting dupe answers" - PostAction.exec_sql <<-SQL + DB.exec <<~SQL DELETE FROM accepted_data WHERE post_id NOT IN ( SELECT post_id FROM ( @@ -656,7 +656,7 @@ class ImportScripts::Lithium < ImportScripts::Base SQL puts "importing accepted answers" - PostAction.exec_sql <<-SQL + DB.exec <<~SQL INSERT into post_custom_fields (name, value, post_id, created_at, updated_at) SELECT 'is_accepted_answer', 'true', a.post_id, current_timestamp, current_timestamp FROM accepted_data a @@ -665,7 +665,7 @@ class ImportScripts::Lithium < ImportScripts::Base SQL puts "marking accepted topics" - PostAction.exec_sql <<-SQL + DB.exec <<~SQL INSERT into topic_custom_fields (name, value, topic_id, created_at, updated_at) SELECT 'accepted_answer_post_id', a.post_id::varchar, p.topic_id, current_timestamp, current_timestamp FROM accepted_data a @@ -797,10 +797,10 @@ class ImportScripts::Lithium < ImportScripts::Base results.map { |r| r["post_id"] }.each_slice(500) do |ids| mapped = ids.map { |id| existing_map[id] }.compact - Topic.exec_sql(" - UPDATE topics SET closed = true - WHERE id IN (SELECT topic_id FROM posts where id in (:ids)) - ", ids: mapped) if mapped.present? + DB.exec(<<~SQL, ids: mapped) if mapped.present? + UPDATE topics SET closed = true + WHERE id IN (SELECT topic_id FROM posts where id in (:ids)) + SQL end end @@ -819,8 +819,8 @@ class ImportScripts::Lithium < ImportScripts::Base WHERE pm.id IS NULL AND f.name = 'import_unique_id' SQL - r = Permalink.exec_sql sql - puts "#{r.cmd_tuples} permalinks to topics added!" + r = DB.exec sql + puts "#{r} permalinks to topics added!" sql = <<-SQL INSERT INTO permalinks (url, post_id, created_at, updated_at) @@ -831,8 +831,8 @@ SQL WHERE pm.id IS NULL AND f.name = 'import_unique_id' SQL - r = Permalink.exec_sql sql - puts "#{r.cmd_tuples} permalinks to posts added!" + r = DB.exec sql + puts "#{r} permalinks to posts added!" end diff --git a/script/import_scripts/modx.rb b/script/import_scripts/modx.rb index 682954bce0f..eeaa21be95a 100644 --- a/script/import_scripts/modx.rb +++ b/script/import_scripts/modx.rb @@ -236,7 +236,7 @@ FROM #{TABLE_PREFIX}discuss_users def not_mark_topics_as_solved puts "", "Marking topics as solved..." - PostAction.exec_sql <<-SQL + DB.exec <<~SQL INSERT INTO topic_custom_fields (name, value, topic_id, created_at, updated_at) SELECT 'accepted_answer_post_id', pcf.post_id, p.topic_id, p.created_at, p.created_at FROM post_custom_fields pcf @@ -469,7 +469,7 @@ FROM #{TABLE_PREFIX}discuss_users WHERE id IN (SELECT topic_id FROM closed_topic_ids) SQL - Topic.exec_sql(sql, closed_topic_ids) + DB.exec(sql, closed_topic_ids) end def not_post_process_posts diff --git a/script/import_scripts/stack_overflow.rb b/script/import_scripts/stack_overflow.rb index 1759f2eec8b..06754fa0b26 100644 --- a/script/import_scripts/stack_overflow.rb +++ b/script/import_scripts/stack_overflow.rb @@ -237,7 +237,7 @@ class ImportScripts::StackOverflow < ImportScripts::Base def mark_topics_as_solved puts "", "Marking topics as solved..." - Topic.exec_sql <<~SQL + DB.exec <<~SQL INSERT INTO topic_custom_fields (name, value, topic_id, created_at, updated_at) SELECT 'accepted_answer_post_id', pcf.post_id, p.topic_id, p.created_at, p.created_at FROM post_custom_fields pcf diff --git a/script/import_scripts/vbulletin.rb b/script/import_scripts/vbulletin.rb index 656c27e7b35..23923d0baa0 100644 --- a/script/import_scripts/vbulletin.rb +++ b/script/import_scripts/vbulletin.rb @@ -182,10 +182,8 @@ EOM next if user_ids_in_group.size == 0 values = user_ids_in_group.map { |user_id| "(#{group.id}, #{user_id}, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)" }.join(",") - User.exec_sql <<-SQL - BEGIN; - INSERT INTO group_users (group_id, user_id, created_at, updated_at) VALUES #{values}; - COMMIT; + DB.exec <<~SQL + INSERT INTO group_users (group_id, user_id, created_at, updated_at) VALUES #{values} SQL Group.reset_counters(group.id, :group_users) @@ -634,7 +632,7 @@ EOM WHERE id IN (SELECT topic_id FROM closed_topic_ids) SQL - Topic.exec_sql(sql, closed_topic_ids) + DB.exec(sql, closed_topic_ids) end def post_process_posts diff --git a/script/import_scripts/vbulletin5.rb b/script/import_scripts/vbulletin5.rb index da1ceea84bf..36a46745b5d 100644 --- a/script/import_scripts/vbulletin5.rb +++ b/script/import_scripts/vbulletin5.rb @@ -418,7 +418,7 @@ class ImportScripts::VBulletin < ImportScripts::Base WHERE id IN (SELECT topic_id FROM closed_topic_ids) SQL - Topic.exec_sql(sql, @closed_topic_ids) + DB.exec(sql, @closed_topic_ids) end def post_process_posts diff --git a/spec/components/concern/has_custom_fields_spec.rb b/spec/components/concern/has_custom_fields_spec.rb index 035e0260c5a..1a55fc9f7c4 100644 --- a/spec/components/concern/has_custom_fields_spec.rb +++ b/spec/components/concern/has_custom_fields_spec.rb @@ -4,8 +4,8 @@ describe HasCustomFields do context "custom_fields" do before do - Topic.exec_sql("create temporary table custom_fields_test_items(id SERIAL primary key)") - Topic.exec_sql("create temporary table custom_fields_test_item_custom_fields(id SERIAL primary key, custom_fields_test_item_id int, name varchar(256) not null, value text)") + DB.exec("create temporary table custom_fields_test_items(id SERIAL primary key)") + DB.exec("create temporary table custom_fields_test_item_custom_fields(id SERIAL primary key, custom_fields_test_item_id int, name varchar(256) not null, value text)") class CustomFieldsTestItem < ActiveRecord::Base include HasCustomFields @@ -17,8 +17,8 @@ describe HasCustomFields do end after do - Topic.exec_sql("drop table custom_fields_test_items") - Topic.exec_sql("drop table custom_fields_test_item_custom_fields") + DB.exec("drop table custom_fields_test_items") + DB.exec("drop table custom_fields_test_item_custom_fields") # import is making my life hard, we need to nuke this out of orbit des = ActiveSupport::DescendantsTracker.class_variable_get :@@direct_descendants @@ -75,7 +75,7 @@ describe HasCustomFields do # should be casted right after saving expect(test_item.custom_fields["a"]).to eq("0") - CustomFieldsTestItem.exec_sql("UPDATE custom_fields_test_item_custom_fields SET value='1' WHERE custom_fields_test_item_id=? AND name='a'", test_item.id) + DB.exec("UPDATE custom_fields_test_item_custom_fields SET value='1' WHERE custom_fields_test_item_id=? AND name='a'", test_item.id) # still the same, did not load expect(test_item.custom_fields["a"]).to eq("0") diff --git a/spec/components/concern/has_search_data_spec.rb b/spec/components/concern/has_search_data_spec.rb index 9cc3089f585..8c5441b2460 100644 --- a/spec/components/concern/has_search_data_spec.rb +++ b/spec/components/concern/has_search_data_spec.rb @@ -3,8 +3,8 @@ require "rails_helper" describe HasSearchData do context "belongs to its model" do before do - Topic.exec_sql("create temporary table model_items(id SERIAL primary key)") - Topic.exec_sql("create temporary table model_item_search_data(model_item_id int primary key, search_data tsvector, raw_data text, locale text)") + DB.exec("create temporary table model_items(id SERIAL primary key)") + DB.exec("create temporary table model_item_search_data(model_item_id int primary key, search_data tsvector, raw_data text, locale text)") class ModelItem < ActiveRecord::Base has_one :model_item_search_data, dependent: :destroy @@ -16,8 +16,8 @@ describe HasSearchData do end after do - Topic.exec_sql("drop table model_items") - Topic.exec_sql("drop table model_item_search_data") + DB.exec("drop table model_items") + DB.exec("drop table model_item_search_data") # import is making my life hard, we need to nuke this out of orbit des = ActiveSupport::DescendantsTracker.class_variable_get :@@direct_descendants diff --git a/spec/components/concern/positionable_spec.rb b/spec/components/concern/positionable_spec.rb index d8433d67115..4ab010a538e 100644 --- a/spec/components/concern/positionable_spec.rb +++ b/spec/components/concern/positionable_spec.rb @@ -12,11 +12,11 @@ describe Positionable do include Positionable end - Topic.exec_sql("create temporary table test_items(id int primary key, position int)") + DB.exec("create temporary table test_items(id int primary key, position int)") end after do - Topic.exec_sql("drop table test_items") + DB.exec("drop table test_items") # import is making my life hard, we need to nuke this out of orbit des = ActiveSupport::DescendantsTracker.class_variable_get :@@direct_descendants @@ -25,7 +25,7 @@ describe Positionable do it "can position stuff correctly" do 5.times do |i| - Topic.exec_sql("insert into test_items(id,position) values(#{i}, #{i})") + DB.exec("insert into test_items(id,position) values(#{i}, #{i})") end expect(positions).to eq([0, 1, 2, 3, 4]) diff --git a/spec/components/concern/searchable_spec.rb b/spec/components/concern/searchable_spec.rb index 365f2b16f87..5c55c7d1ff5 100644 --- a/spec/components/concern/searchable_spec.rb +++ b/spec/components/concern/searchable_spec.rb @@ -3,8 +3,8 @@ require "rails_helper" describe Searchable do context "has search data" do before do - Topic.exec_sql("create temporary table searchable_records(id SERIAL primary key)") - Topic.exec_sql("create temporary table searchable_record_search_data(searchable_record_id int primary key, search_data tsvector, raw_data text, locale text)") + DB.exec("create temporary table searchable_records(id SERIAL primary key)") + DB.exec("create temporary table searchable_record_search_data(searchable_record_id int primary key, search_data tsvector, raw_data text, locale text)") class SearchableRecord < ActiveRecord::Base include Searchable @@ -17,8 +17,8 @@ describe Searchable do end after do - Topic.exec_sql("drop table searchable_records") - Topic.exec_sql("drop table searchable_record_search_data") + DB.exec("drop table searchable_records") + DB.exec("drop table searchable_record_search_data") # import is making my life hard, we need to nuke this out of orbit des = ActiveSupport::DescendantsTracker.class_variable_get :@@direct_descendants diff --git a/spec/components/migration/column_dropper_spec.rb b/spec/components/migration/column_dropper_spec.rb index 9a0bd536e2b..b83aa32fc75 100644 --- a/spec/components/migration/column_dropper_spec.rb +++ b/spec/components/migration/column_dropper_spec.rb @@ -4,7 +4,7 @@ require_dependency 'migration/column_dropper' RSpec.describe Migration::ColumnDropper do def has_column?(table, column) - ActiveRecord::Base.exec_sql(<<~SQL, table: table, column: column).to_a.length == 1 + DB.exec(<<~SQL, table: table, column: column) == 1 SELECT 1 FROM INFORMATION_SCHEMA.COLUMNS WHERE @@ -15,7 +15,7 @@ RSpec.describe Migration::ColumnDropper do end def update_first_migration_date(created_at) - ActiveRecord::Base.exec_sql(<<~SQL, created_at: created_at) + DB.exec(<<~SQL, created_at: created_at) UPDATE schema_migration_details SET created_at = :created_at WHERE id = (SELECT MIN(id) @@ -25,15 +25,13 @@ RSpec.describe Migration::ColumnDropper do describe ".drop" do let(:migration_name) do - ActiveRecord::Base - .exec_sql("SELECT name FROM schema_migration_details ORDER BY id DESC LIMIT 1") - .getvalue(0, 0) + DB.query_single("SELECT name FROM schema_migration_details ORDER BY id DESC LIMIT 1").first end before do - Topic.exec_sql "ALTER TABLE topics ADD COLUMN junk int" + DB.exec "ALTER TABLE topics ADD COLUMN junk int" - ActiveRecord::Base.exec_sql(<<~SQL, name: migration_name, created_at: 15.minutes.ago) + DB.exec(<<~SQL, name: migration_name, created_at: 15.minutes.ago) UPDATE schema_migration_details SET created_at = :created_at WHERE name = :name @@ -114,7 +112,7 @@ RSpec.describe Migration::ColumnDropper do let(:table_name) { "table_with_readonly_column" } before do - ActiveRecord::Base.exec_sql <<~SQL + DB.exec <<~SQL CREATE TABLE #{table_name} (topic_id INTEGER, email TEXT); INSERT INTO #{table_name} (topic_id, email) @@ -127,16 +125,14 @@ RSpec.describe Migration::ColumnDropper do after do ActiveRecord::Base.connection.reset! - ActiveRecord::Base.exec_sql <<~SQL + DB.exec <<~SQL DROP TABLE IF EXISTS #{table_name}; DROP TRIGGER IF EXISTS #{table_name}_email_readonly ON #{table_name}; SQL end it 'should be droppable' do - name = Topic - .exec_sql("SELECT name FROM schema_migration_details LIMIT 1") - .getvalue(0, 0) + name = DB.query_single("SELECT name FROM schema_migration_details LIMIT 1").first dropped_proc_called = false Migration::ColumnDropper.drop( @@ -152,7 +148,7 @@ RSpec.describe Migration::ColumnDropper do end it 'should prevent updates to the readonly column' do expect do - ActiveRecord::Base.connection.raw_connection.exec <<~SQL + DB.exec <<~SQL UPDATE #{table_name} SET email = 'testing@email.com' WHERE topic_id = 1; @@ -164,7 +160,7 @@ RSpec.describe Migration::ColumnDropper do end it 'should allow updates to the other columns' do - ActiveRecord::Base.exec_sql <<~SQL + DB.exec <<~SQL UPDATE #{table_name} SET topic_id = 2 WHERE topic_id = 1 @@ -188,14 +184,14 @@ RSpec.describe Migration::ColumnDropper do end it 'should allow insertions to the other columns' do - ActiveRecord::Base.exec_sql <<~SQL + DB.exec <<~SQL INSERT INTO #{table_name} (topic_id) VALUES (2); SQL expect( - ActiveRecord::Base.exec_sql("SELECT * FROM #{table_name} WHERE topic_id = 2;").values - ).to include([2, nil]) + DB.query_single("SELECT topic_id FROM #{table_name} WHERE topic_id = 2") + ).to eq([2]) end end end diff --git a/spec/components/migration/table_dropper_spec.rb b/spec/components/migration/table_dropper_spec.rb index 0b798f82bcb..7db3c64f1de 100644 --- a/spec/components/migration/table_dropper_spec.rb +++ b/spec/components/migration/table_dropper_spec.rb @@ -11,11 +11,11 @@ describe Migration::TableDropper do table_name = '#{table_name}' SQL - ActiveRecord::Base.exec_sql(sql).to_a.length > 0 + DB.exec(sql) > 0 end def update_first_migration_date(created_at) - ActiveRecord::Base.exec_sql(<<~SQL, created_at: created_at) + DB.exec(<<~SQL, created_at: created_at) UPDATE schema_migration_details SET created_at = :created_at WHERE id = (SELECT MIN(id) @@ -24,19 +24,17 @@ describe Migration::TableDropper do end def create_new_table - ActiveRecord::Base.exec_sql "CREATE TABLE table_with_new_name (topic_id INTEGER)" + DB.exec "CREATE TABLE table_with_new_name (topic_id INTEGER)" end let(:migration_name) do - ActiveRecord::Base - .exec_sql("SELECT name FROM schema_migration_details ORDER BY id DESC LIMIT 1") - .getvalue(0, 0) + DB.query_single("SELECT name FROM schema_migration_details ORDER BY id DESC LIMIT 1").first end before do - ActiveRecord::Base.exec_sql "CREATE TABLE table_with_old_name (topic_id INTEGER)" + DB.exec "CREATE TABLE table_with_old_name (topic_id INTEGER)" - ActiveRecord::Base.exec_sql(<<~SQL, name: migration_name, created_at: 15.minutes.ago) + DB.exec(<<~SQL, name: migration_name, created_at: 15.minutes.ago) UPDATE schema_migration_details SET created_at = :created_at WHERE name = :name diff --git a/spec/components/search_spec.rb b/spec/components/search_spec.rb index 0a70342d403..9ad75bf58cf 100644 --- a/spec/components/search_spec.rb +++ b/spec/components/search_spec.rb @@ -895,7 +895,7 @@ describe Search do str << "page page on Atmosphere](https://atmospherejs.com/grigio/babel)xxx: aaa.js:222 aaa'\"bbb" ts_query = Search.ts_query(term: str, ts_config: "simple") - Post.exec_sql("SELECT to_tsvector('bbb') @@ " << ts_query) + DB.exec("SELECT to_tsvector('bbb') @@ " << ts_query) end context '#word_to_date' do diff --git a/spec/jobs/toggle_topic_closed_spec.rb b/spec/jobs/toggle_topic_closed_spec.rb index 31908796cce..24b9d3c307b 100644 --- a/spec/jobs/toggle_topic_closed_spec.rb +++ b/spec/jobs/toggle_topic_closed_spec.rb @@ -53,7 +53,7 @@ describe Jobs::ToggleTopicClosed do user: admin ) - freeze_time(1.hour.from_now) do + freeze_time(61.minutes.from_now) do described_class.new.execute( topic_timer_id: topic.public_topic_timer.id, state: false diff --git a/spec/models/group_spec.rb b/spec/models/group_spec.rb index 4027250bef9..30a3ba3f42e 100644 --- a/spec/models/group_spec.rb +++ b/spec/models/group_spec.rb @@ -406,7 +406,7 @@ describe Group do user = Fabricate(:user) user.change_trust_level!(TrustLevel[2]) - Group.exec_sql("UPDATE groups SET user_count = 0 WHERE id = #{Group::AUTO_GROUPS[:trust_level_2]}") + DB.exec("UPDATE groups SET user_count = 0 WHERE id = #{Group::AUTO_GROUPS[:trust_level_2]}") Group.refresh_automatic_groups! diff --git a/spec/models/post_spec.rb b/spec/models/post_spec.rb index f2bd06587da..0515e53cffb 100644 --- a/spec/models/post_spec.rb +++ b/spec/models/post_spec.rb @@ -1014,7 +1014,7 @@ describe Post do first_baked = post.baked_at first_cooked = post.cooked - Post.exec_sql("UPDATE posts SET cooked = 'frogs' WHERE id = ?", post.id) + DB.exec("UPDATE posts SET cooked = 'frogs' WHERE id = ?", [ post.id ]) post.reload post.expects(:publish_change_to_clients!).with(:rebaked) diff --git a/spec/models/topic_link_spec.rb b/spec/models/topic_link_spec.rb index 606ba5f7bae..d3d53a07280 100644 --- a/spec/models/topic_link_spec.rb +++ b/spec/models/topic_link_spec.rb @@ -330,7 +330,7 @@ http://b.com/#{'a' * 500} array = TopicLink.topic_map(Guardian.new, post.topic_id) expect(array.length).to eq(6) - expect(array[0]["clicks"]).to eq(1) + expect(array[0].clicks).to eq(1) end it 'secures internal links correctly' do diff --git a/spec/models/topic_user_spec.rb b/spec/models/topic_user_spec.rb index c0ed6c986ba..ed9de27c3ad 100644 --- a/spec/models/topic_user_spec.rb +++ b/spec/models/topic_user_spec.rb @@ -311,8 +311,10 @@ describe TopicUser do it 'should update tracking state when you reply' do new_user.user_option.update_column(:notification_level_when_replying, 3) post_creator.create - TopicUser.exec_sql("UPDATE topic_users set notification_level=2 - WHERE topic_id = :topic_id AND user_id = :user_id", topic_id: topic_new_user.topic_id, user_id: topic_new_user.user_id) + DB.exec("UPDATE topic_users set notification_level=2 + WHERE topic_id = :topic_id AND user_id = :user_id", + topic_id: topic_new_user.topic_id, user_id: topic_new_user.user_id) + TopicUser.auto_notification(topic_new_user.user_id, topic_new_user.topic_id, TopicUser.notification_reasons[:created_post], TopicUser.notification_levels[:watching]) tu = TopicUser.find_by(user_id: topic_new_user.user_id, topic_id: topic_new_user.topic_id) @@ -322,7 +324,7 @@ describe TopicUser do it 'should not update tracking state when you reply' do new_user.user_option.update_column(:notification_level_when_replying, 3) post_creator.create - TopicUser.exec_sql("UPDATE topic_users set notification_level=3 + DB.exec("UPDATE topic_users set notification_level=3 WHERE topic_id = :topic_id AND user_id = :user_id", topic_id: topic_new_user.topic_id, user_id: topic_new_user.user_id) TopicUser.auto_notification(topic_new_user.user_id, topic_new_user.topic_id, TopicUser.notification_reasons[:created_post], TopicUser.notification_levels[:tracking]) @@ -333,7 +335,7 @@ describe TopicUser do it 'should not update tracking state when state manually set to normal you reply' do new_user.user_option.update_column(:notification_level_when_replying, 3) post_creator.create - TopicUser.exec_sql("UPDATE topic_users set notification_level=1 + DB.exec("UPDATE topic_users set notification_level=1 WHERE topic_id = :topic_id AND user_id = :user_id", topic_id: topic_new_user.topic_id, user_id: topic_new_user.user_id) TopicUser.auto_notification(topic_new_user.user_id, topic_new_user.topic_id, TopicUser.notification_reasons[:created_post], TopicUser.notification_levels[:tracking]) @@ -344,7 +346,7 @@ describe TopicUser do it 'should not update tracking state when state manually set to muted you reply' do new_user.user_option.update_column(:notification_level_when_replying, 3) post_creator.create - TopicUser.exec_sql("UPDATE topic_users set notification_level=0 + DB.exec("UPDATE topic_users set notification_level=0 WHERE topic_id = :topic_id AND user_id = :user_id", topic_id: topic_new_user.topic_id, user_id: topic_new_user.user_id) TopicUser.auto_notification(topic_new_user.user_id, topic_new_user.topic_id, TopicUser.notification_reasons[:created_post], TopicUser.notification_levels[:tracking]) @@ -417,7 +419,7 @@ describe TopicUser do p2 = Fabricate(:post, user: p1.user, topic: p1.topic, post_number: 2) p1.topic.notifier.watch_topic!(p1.user_id) - TopicUser.exec_sql("UPDATE topic_users set highest_seen_post_number=1, last_read_post_number=0 + DB.exec("UPDATE topic_users set highest_seen_post_number=1, last_read_post_number=0 WHERE topic_id = :topic_id AND user_id = :user_id", topic_id: p1.topic_id, user_id: p1.user_id) [p1, p2].each do |p|