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
This commit is contained in:
Sam 2018-06-19 16:13:14 +10:00
parent cc3fc87dd7
commit 5f64fd0a21
112 changed files with 782 additions and 763 deletions

View File

@ -78,7 +78,8 @@ gem 'omniauth-oauth2', require: false
gem 'omniauth-google-oauth2' gem 'omniauth-google-oauth2'
gem 'oj' gem 'oj'
gem 'pg', '~> 0.21.0' gem 'pg'
gem 'mini_sql'
gem 'pry-rails', require: false gem 'pry-rails', require: false
gem 'r2', '~> 0.2.5', require: false gem 'r2', '~> 0.2.5', require: false
gem 'rake' gem 'rake'

View File

@ -176,6 +176,7 @@ GEM
mini_portile2 (2.3.0) mini_portile2 (2.3.0)
mini_racer (0.1.15) mini_racer (0.1.15)
libv8 (~> 6.3) libv8 (~> 6.3)
mini_sql (0.1.4)
mini_suffix (0.3.0) mini_suffix (0.3.0)
ffi (~> 1.9) ffi (~> 1.9)
minitest (5.11.3) minitest (5.11.3)
@ -240,7 +241,7 @@ GEM
parallel (1.12.1) parallel (1.12.1)
parser (2.5.1.0) parser (2.5.1.0)
ast (~> 2.4.0) ast (~> 2.4.0)
pg (0.21.0) pg (1.0.0)
powerpack (0.1.1) powerpack (0.1.1)
progress (3.4.0) progress (3.4.0)
pry (0.10.4) pry (0.10.4)
@ -453,6 +454,7 @@ DEPENDENCIES
message_bus message_bus
mini_mime mini_mime
mini_racer mini_racer
mini_sql
mini_suffix mini_suffix
minitest minitest
mocha mocha
@ -471,7 +473,7 @@ DEPENDENCIES
omniauth-twitter omniauth-twitter
onebox (= 1.8.48) onebox (= 1.8.48)
openid-redis-store openid-redis-store
pg (~> 0.21.0) pg
pry-nav pry-nav
pry-rails pry-rails
puma puma

View File

@ -4,19 +4,6 @@ class Admin::DiagnosticsController < Admin::AdminController
layout false layout false
skip_before_action :check_xhr 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 def memory_stats
text = nil text = nil

View File

@ -77,7 +77,7 @@ class Admin::UsersController < Admin::AdminController
) )
SQL SQL
UserHistory.exec_sql( DB.exec(
sql, sql,
UserHistory.actions.slice( UserHistory.actions.slice(
:silence_user, :silence_user,

View File

@ -1,8 +1,8 @@
module Jobs module Jobs
class CreateTagsSearchIndex < Jobs::Onceoff class CreateTagsSearchIndex < Jobs::Onceoff
def execute_onceoff(args) def execute_onceoff(args)
Tag.exec_sql('select id, name from tags').each do |t| DB.query('select id, name from tags').each do |t|
SearchIndexer.update_tags_index(t['id'], t['name']) SearchIndexer.update_tags_index(t.id, t.name)
end end
end end
end end

View File

@ -15,7 +15,7 @@ UPDATE user_stats
) )
SQL SQL
UserStat.exec_sql(sql) DB.exec(sql)
end end
end end
end end

View File

@ -24,7 +24,7 @@ module Jobs
end end
end end
User.exec_sql <<~SQL DB.exec <<~SQL
INSERT INTO user_emails ( INSERT INTO user_emails (
user_id, user_id,
email, email,

View File

@ -6,7 +6,7 @@ module Jobs
def execute_onceoff(args) def execute_onceoff(args)
return unless SiteSetting.enable_badges 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(*) SELECT ub.user_id, MIN(granted_at) AS first_granted_at, COUNT(*)
FROM user_badges AS ub FROM user_badges AS ub
WHERE ub.badge_id = #{Badge::Anniversary} WHERE ub.badge_id = #{Badge::Anniversary}
@ -15,17 +15,17 @@ module Jobs
SQL SQL
users.to_a.each do |u| users.to_a.each do |u|
first = Time.zone.parse(u['first_granted_at']) first = u.first_granted_at
badges = UserBadge.where( badges = UserBadge.where(
"badge_id = ? AND user_id = ? AND granted_at > ?", "badge_id = ? AND user_id = ? AND granted_at > ?",
Badge::Anniversary, Badge::Anniversary,
u['user_id'], u.user_id,
first first
).order('granted_at') ).order('granted_at')
badges.each_with_index do |b, idx| badges.each_with_index do |b, idx|
award_date = (first + (idx + 1).years) 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
end end

View File

@ -1,9 +1,9 @@
module Jobs module Jobs
class InitCategoryTagStats < Jobs::Onceoff class InitCategoryTagStats < Jobs::Onceoff
def execute_onceoff(args) 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) INSERT INTO category_tag_stats (category_id, tag_id, topic_count)
SELECT topics.category_id, tags.id, COUNT(topics.id) SELECT topics.category_id, tags.id, COUNT(topics.id)
FROM tags FROM tags

View File

@ -1,9 +1,9 @@
module Jobs module Jobs
class MigrateCensoredWords < Jobs::Onceoff class MigrateCensoredWords < Jobs::Onceoff
def execute_onceoff(args) 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 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]) WatchedWord.create(word: word, action: WatchedWord.actions[:censor])
end end
end end

View File

@ -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) AND EXISTS (SELECT 1 FROM user_visits visits WHERE visits.user_id = uv1.user_id AND visits.posts_read > 0 LIMIT 1)
SQL SQL
UserVisit.exec_sql(sql) DB.exec(sql)
end end
end end
end end

View File

@ -60,7 +60,7 @@ module Jobs
new_username: @new_username new_username: @new_username
} }
Notification.exec_sql(<<~SQL, params) DB.exec(<<~SQL, params)
UPDATE notifications UPDATE notifications
SET data = (data :: JSONB || SET data = (data :: JSONB ||
jsonb_strip_nulls( jsonb_strip_nulls(
@ -88,7 +88,7 @@ module Jobs
end end
def update_post_custom_fields 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 UPDATE post_custom_fields
SET value = :new_username SET value = :new_username
WHERE name = 'action_code_who' AND value = :old_username WHERE name = 'action_code_who' AND value = :old_username

View File

@ -7,7 +7,7 @@ module Jobs
WebCrawlerRequest.where('date < ?', WebCrawlerRequest.max_record_age.ago).delete_all WebCrawlerRequest.where('date < ?', WebCrawlerRequest.max_record_age.ago).delete_all
# keep count of only the top user agents # keep count of only the top user agents
WebCrawlerRequest.exec_sql <<~SQL DB.exec <<~SQL
WITH ranked_requests AS ( WITH ranked_requests AS (
SELECT row_number() OVER (ORDER BY count DESC) as row_number, id SELECT row_number() OVER (ORDER BY count DESC) as row_number, id
FROM web_crawler_requests FROM web_crawler_requests

View File

@ -13,7 +13,7 @@ module Jobs
fmt_end_date = end_date.iso8601(6) fmt_end_date = end_date.iso8601(6)
fmt_start_date = start_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 SELECT u.id AS user_id
FROM users AS u FROM users AS u
INNER JOIN posts AS p ON p.user_id = u.id 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 HAVING COUNT(p.id) > 0 AND COUNT(ub.id) = 0
SQL SQL
user_ids = results.column_values(0)
results.clear
User.where(id: user_ids).find_each do |user| User.where(id: user_ids).find_each do |user|
BadgeGranter.grant(badge, user, created_at: end_date) BadgeGranter.grant(badge, user, created_at: end_date)
end end

View File

@ -55,7 +55,7 @@ module Jobs
ELSE 1.0 ELSE 1.0
END END
ELSE 0 ELSE 0
END) / (5 + COUNT(DISTINCT p.id)) AS score END) / (5 + COUNT(DISTINCT p.id))::float AS score
FROM users AS u FROM users AS u
INNER JOIN user_stats AS us ON u.id = us.user_id INNER JOIN user_stats AS us ON u.id = us.user_id
LEFT OUTER JOIN posts AS p ON p.user_id = u.id LEFT OUTER JOIN posts AS p ON p.user_id = u.id
@ -82,10 +82,7 @@ module Jobs
LIMIT #{MAX_AWARDED} LIMIT #{MAX_AWARDED}
SQL SQL
result = User.exec_sql(sql) Hash[*DB.query_single(sql)]
rval = result.map { |r| [r['id'].to_i, r['score'].to_f] }.to_h
result.clear
rval
end end
end end

View File

@ -136,7 +136,7 @@ class Badge < ActiveRecord::Base
end end
def self.ensure_consistency! def self.ensure_consistency!
exec_sql <<-SQL.squish DB.exec <<~SQL
DELETE FROM user_badges DELETE FROM user_badges
USING user_badges ub USING user_badges ub
LEFT JOIN users u ON u.id = ub.user_id LEFT JOIN users u ON u.id = ub.user_id
@ -144,7 +144,7 @@ class Badge < ActiveRecord::Base
AND user_badges.id = ub.id AND user_badges.id = ub.id
SQL SQL
exec_sql <<-SQL.squish DB.exec <<~SQL
WITH X AS ( WITH X AS (
SELECT badge_id SELECT badge_id
, COUNT(user_id) users , COUNT(user_id) users

View File

@ -143,14 +143,14 @@ class Category < ActiveRecord::Base
.group("topics.category_id") .group("topics.category_id")
.visible.to_sql .visible.to_sql
Category.exec_sql <<-SQL DB.exec <<~SQL
UPDATE categories c UPDATE categories c
SET topic_count = x.topic_count, SET topic_count = x.topic_count,
post_count = x.post_count post_count = x.post_count
FROM (#{topics_with_post_count}) x FROM (#{topics_with_post_count}) x
WHERE x.category_id = c.id WHERE x.category_id = c.id
AND (c.topic_count <> x.topic_count OR c.post_count <> x.post_count) AND (c.topic_count <> x.topic_count OR c.post_count <> x.post_count)
SQL SQL
# Yes, there are a lot of queries happening below. # Yes, there are a lot of queries happening below.
# Performing a lot of queries is actually faster than using one big update # Performing a lot of queries is actually faster than using one big update

View File

@ -19,7 +19,7 @@ class CategoryTagStat < ActiveRecord::Base
SQL SQL
tag_ids = topic.tags.map(&:id) 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| (tag_ids - updated_tag_ids).each do |tag_id|
CategoryTagStat.create!(tag_id: tag_id, category_id: to_category_id, topic_count: 1) 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 # Recalculate all topic counts if they got out of sync
def self.update_topic_counts def self.update_topic_counts
CategoryTagStat.exec_sql <<~SQL DB.exec <<~SQL
UPDATE category_tag_stats stats UPDATE category_tag_stats stats
SET topic_count = x.topic_count SET topic_count = x.topic_count
FROM ( FROM (

View File

@ -155,14 +155,14 @@ SQL
end end
def self.ensure_consistency! def self.ensure_consistency!
exec_sql <<SQL DB.exec <<~SQL
DELETE FROM category_users DELETE FROM category_users
WHERE user_id IN ( WHERE user_id IN (
SELECT cu.user_id FROM category_users cu SELECT cu.user_id FROM category_users cu
LEFT JOIN users u ON u.id = cu.user_id LEFT JOIN users u ON u.id = cu.user_id
WHERE u.id IS NULL WHERE u.id IS NULL
) )
SQL SQL
end end
end end

View File

@ -12,13 +12,13 @@ module Positionable
position = [[position_arg, 0].max, self.class.count - 1].min position = [[position_arg, 0].max, self.class.count - 1].min
if self.position.nil? || position > (self.position) if self.position.nil? || position > (self.position)
self.exec_sql " DB.exec "
UPDATE #{self.class.table_name} UPDATE #{self.class.table_name}
SET position = position - 1 SET position = position - 1
WHERE position > :current_position and position <= :new_position", WHERE position > :current_position and position <= :new_position",
current_position: self.position, new_position: position current_position: self.position, new_position: position
elsif position < self.position elsif position < self.position
self.exec_sql " DB.exec "
UPDATE #{self.class.table_name} UPDATE #{self.class.table_name}
SET position = position + 1 SET position = position + 1
WHERE position >= :new_position and position < :current_position", WHERE position >= :new_position and position < :current_position",
@ -28,7 +28,7 @@ module Positionable
return return
end end
self.exec_sql " DB.exec "
UPDATE #{self.class.table_name} UPDATE #{self.class.table_name}
SET position = :position SET position = :position
WHERE id = :id", id: id, position: position WHERE id = :id", id: id, position: position

View File

@ -1,3 +1,5 @@
# frozen_string_literal: true
class DirectoryItem < ActiveRecord::Base class DirectoryItem < ActiveRecord::Base
belongs_to :user belongs_to :user
has_one :user_stat, foreign_key: :user_id, primary_key: :user_id has_one :user_stat, foreign_key: :user_id, primary_key: :user_id
@ -42,7 +44,7 @@ class DirectoryItem < ActiveRecord::Base
ActiveRecord::Base.transaction do ActiveRecord::Base.transaction do
# Delete records that belonged to users who have been deleted # 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 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) 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 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] di.period_type = :period_type", period_type: period_types[period_type]
# Create new records for users who don't have one yet # 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 SELECT
:period_type, :period_type,
u.id, u.id,
@ -72,7 +74,7 @@ class DirectoryItem < ActiveRecord::Base
# TODO # TODO
# WARNING: post_count is a wrong name, it should be reply_count (excluding topic post) # 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, 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 = :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, 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] regular_post_type: Post.types[:regular]
if period_type == :all if period_type == :all
exec_sql <<SQL DB.exec <<~SQL
UPDATE user_stats s UPDATE user_stats s
SET likes_given = d.likes_given, SET likes_given = d.likes_given,
likes_received = d.likes_received, likes_received = d.likes_received,
topic_count = d.topic_count, topic_count = d.topic_count,
post_count = d.post_count post_count = d.post_count
FROM directory_items d FROM directory_items d
WHERE s.user_id = d.user_id AND WHERE s.user_id = d.user_id AND
d.period_type = 1 AND d.period_type = 1 AND
( s.likes_given <> d.likes_given OR ( s.likes_given <> d.likes_given OR
s.likes_received <> d.likes_received OR s.likes_received <> d.likes_received OR
s.topic_count <> d.topic_count OR s.topic_count <> d.topic_count OR
s.post_count <> d.post_count s.post_count <> d.post_count
) )
SQL
SQL
end end
end end
end end

View File

@ -1,3 +1,5 @@
# frozen_string_literal: true
class Draft < ActiveRecord::Base class Draft < ActiveRecord::Base
NEW_TOPIC = 'new_topic' NEW_TOPIC = 'new_topic'
NEW_PRIVATE_MESSAGE = 'new_private_message' NEW_PRIVATE_MESSAGE = 'new_private_message'
@ -7,7 +9,7 @@ class Draft < ActiveRecord::Base
d = find_draft(user, key) d = find_draft(user, key)
if d if d
return if d.sequence > sequence return if d.sequence > sequence
exec_sql("UPDATE drafts DB.exec("UPDATE drafts
SET data = :data, SET data = :data,
sequence = :sequence, sequence = :sequence,
revisions = revisions + 1 revisions = revisions + 1
@ -15,6 +17,8 @@ class Draft < ActiveRecord::Base
else else
Draft.create!(user_id: user.id, draft_key: key, data: data, sequence: sequence) Draft.create!(user_id: user.id, draft_key: key, data: data, sequence: sequence)
end end
true
end end
def self.get(user, key, sequence) def self.get(user, key, sequence)
@ -40,7 +44,7 @@ class Draft < ActiveRecord::Base
end end
def self.cleanup! 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 SELECT max(s.sequence) from draft_sequences s
WHERE s.draft_key = drafts.draft_key AND WHERE s.draft_key = drafts.draft_key AND
s.user_id = drafts.user_id s.user_id = drafts.user_id

View File

@ -11,7 +11,7 @@ class DraftSequence < ActiveRecord::Base
c.sequence ||= 0 c.sequence ||= 0
c.sequence += 1 c.sequence += 1
c.save! 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 c.sequence
end end
@ -22,8 +22,8 @@ class DraftSequence < ActiveRecord::Base
user_id = user.id unless user.is_a?(Integer) user_id = user.id unless user.is_a?(Integer)
# perf critical path # perf critical path
r = exec_sql('select sequence from draft_sequences where user_id = ? and draft_key = ?', user_id, key).values r, _ = DB.query_single('select sequence from draft_sequences where user_id = ? and draft_key = ?', user_id, key)
r.length.zero? ? 0 : r[0][0] r.to_i
end end
end end

View File

@ -12,7 +12,7 @@ class EmojiSetSiteSetting < EnumSiteSetting
after = "/images/emoji/#{site_setting.value}/" after = "/images/emoji/#{site_setting.value}/"
Scheduler::Defer.later("Fix Emoji Links") do 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, before: before,
after: after, after: after,
like: "%#{before}%" like: "%#{before}%"

View File

@ -296,7 +296,7 @@ class Group < ActiveRecord::Base
"SELECT id FROM users WHERE id <= 0 OR trust_level < #{id - 10}" "SELECT id FROM users WHERE id <= 0 OR trust_level < #{id - 10}"
end end
exec_sql <<-SQL DB.exec <<-SQL
DELETE FROM group_users DELETE FROM group_users
USING (#{remove_subquery}) X USING (#{remove_subquery}) X
WHERE group_id = #{group.id} WHERE group_id = #{group.id}
@ -318,7 +318,7 @@ class Group < ActiveRecord::Base
"SELECT id FROM users WHERE id > 0" "SELECT id FROM users WHERE id > 0"
end end
exec_sql <<-SQL DB.exec <<-SQL
INSERT INTO group_users (group_id, user_id, created_at, updated_at) INSERT INTO group_users (group_id, user_id, created_at, updated_at)
SELECT #{group.id}, X.id, now(), now() SELECT #{group.id}, X.id, now(), now()
FROM group_users FROM group_users
@ -341,7 +341,7 @@ class Group < ActiveRecord::Base
end end
def self.reset_all_counters! def self.reset_all_counters!
exec_sql <<-SQL DB.exec <<-SQL
WITH X AS ( WITH X AS (
SELECT group_id SELECT group_id
, COUNT(user_id) users , COUNT(user_id) users
@ -362,7 +362,7 @@ class Group < ActiveRecord::Base
end end
def self.refresh_has_messages! def self.refresh_has_messages!
exec_sql <<-SQL DB.exec <<-SQL
UPDATE groups g SET has_messages = false UPDATE groups g SET has_messages = false
WHERE NOT EXISTS (SELECT tg.id WHERE NOT EXISTS (SELECT tg.id
FROM topic_allowed_groups tg FROM topic_allowed_groups tg
@ -534,7 +534,7 @@ class Group < ActiveRecord::Base
) )
SQL 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 = {} user_attributes = {}
@ -551,7 +551,7 @@ class Group < ActiveRecord::Base
end end
# update group user count # update group user count
Group.exec_sql <<-SQL.squish DB.exec <<~SQL
UPDATE groups g UPDATE groups g
SET user_count = SET user_count =
(SELECT COUNT(gu.user_id) (SELECT COUNT(gu.user_id)
@ -605,9 +605,10 @@ class Group < ActiveRecord::Base
name_lower = self.name.downcase name_lower = self.name.downcase
if self.will_save_change_to_name? && self.name_was&.downcase != name_lower 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 User::USERNAME_EXISTS_SQL, username: name_lower
).values.present? ) > 0
if existing if existing
errors.add(:name, I18n.t("activerecord.errors.messages.taken")) errors.add(:name, I18n.t("activerecord.errors.messages.taken"))
@ -649,15 +650,15 @@ class Group < ActiveRecord::Base
return if new_record? && !self.title.present? return if new_record? && !self.title.present?
if self.saved_change_to_title? if self.saved_change_to_title?
sql = <<-SQL.squish sql = <<~SQL
UPDATE users UPDATE users
SET title = :title SET title = :title
WHERE (title = :title_was OR title = '' OR title IS NULL) WHERE (title = :title_was OR title = '' OR title IS NULL)
AND COALESCE(title,'') <> COALESCE(:title,'') AND COALESCE(title,'') <> COALESCE(:title,'')
AND id IN (SELECT user_id FROM group_users WHERE group_id = :id) AND id IN (SELECT user_id FROM group_users WHERE group_id = :id)
SQL 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
end end
@ -666,18 +667,19 @@ class Group < ActiveRecord::Base
if self.saved_change_to_primary_group? if self.saved_change_to_primary_group?
sql = <<~SQL sql = <<~SQL
UPDATE users UPDATE users
/*set*/ /*set*/
/*where*/ /*where*/
SQL SQL
builder = SqlBuilder.new(sql) builder = DB.build(sql)
builder.where(" builder.where(<<~SQL, id: id)
id IN ( id IN (
SELECT user_id SELECT user_id
FROM group_users FROM group_users
WHERE group_id = :id WHERE group_id = :id
)", id: id) )
SQL
if primary_group if primary_group
builder.set("primary_group_id = :id") builder.set("primary_group_id = :id")

View File

@ -25,7 +25,7 @@ class GroupUser < ActiveRecord::Base
def set_primary_group def set_primary_group
if group.primary_group if group.primary_group
self.class.exec_sql(" DB.exec("
UPDATE users UPDATE users
SET primary_group_id = :id SET primary_group_id = :id
WHERE id = :user_id", WHERE id = :user_id",
@ -35,7 +35,7 @@ class GroupUser < ActiveRecord::Base
end end
def remove_primary_group def remove_primary_group
self.class.exec_sql(" DB.exec("
UPDATE users UPDATE users
SET primary_group_id = NULL SET primary_group_id = NULL
WHERE id = :user_id AND primary_group_id = :id", WHERE id = :user_id AND primary_group_id = :id",
@ -45,7 +45,7 @@ class GroupUser < ActiveRecord::Base
def remove_title def remove_title
if group.title.present? if group.title.present?
self.class.exec_sql(" DB.exec("
UPDATE users SET title = NULL UPDATE users SET title = NULL
WHERE title = :title AND id = :id", WHERE title = :title AND id = :id",
id: user_id, title: group.title id: user_id, title: group.title
@ -55,7 +55,7 @@ class GroupUser < ActiveRecord::Base
def update_title def update_title
if group.title.present? if group.title.present?
self.class.exec_sql(" DB.exec("
UPDATE users SET title = :title UPDATE users SET title = :title
WHERE (title IS NULL OR title = '') AND id = :id", WHERE (title IS NULL OR title = '') AND id = :id",
id: user_id, title: group.title id: user_id, title: group.title

View File

@ -89,10 +89,10 @@ class IncomingLink < ActiveRecord::Base
# Internal: Update appropriate link counts. # Internal: Update appropriate link counts.
def update_link_counts def update_link_counts
exec_sql("UPDATE topics DB.exec("UPDATE topics
SET incoming_link_count = incoming_link_count + 1 SET incoming_link_count = incoming_link_count + 1
WHERE id = (SELECT topic_id FROM posts where id = ?)", post_id) 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 SET incoming_link_count = incoming_link_count + 1
WHERE id = ?", post_id) WHERE id = ?", post_id)
end end

View File

@ -19,10 +19,10 @@ class Notification < ActiveRecord::Base
after_commit :refresh_notification_count, on: [:create, :update, :destroy] after_commit :refresh_notification_count, on: [:create, :update, :destroy]
def self.ensure_consistency! def self.ensure_consistency!
Notification.exec_sql <<-SQL DB.exec(<<~SQL, Notification.types[:private_message])
DELETE DELETE
FROM notifications n FROM notifications n
WHERE notification_type = #{Notification.types[:private_message]} WHERE notification_type = ?
AND NOT EXISTS ( AND NOT EXISTS (
SELECT 1 SELECT 1
FROM posts p FROM posts p
@ -152,17 +152,15 @@ class Notification < ActiveRecord::Base
if notifications.present? if notifications.present?
ids = Notification.exec_sql(" ids = DB.query_single(<<~SQL, count.to_i)
SELECT n.id FROM notifications n SELECT n.id FROM notifications n
WHERE WHERE
n.notification_type = 6 AND n.notification_type = 6 AND
n.user_id = #{user.id.to_i} AND n.user_id = #{user.id.to_i} AND
NOT read NOT read
ORDER BY n.id ASC ORDER BY n.id ASC
LIMIT #{count.to_i} LIMIT ?
").values.map do |x, _| SQL
x.to_i
end
if ids.length > 0 if ids.length > 0
notifications += user notifications += user

View File

@ -669,17 +669,19 @@ class Post < ActiveRecord::Base
end end
def reply_history(max_replies = 100, guardian = nil) def reply_history(max_replies = 100, guardian = nil)
post_ids = Post.exec_sql("WITH RECURSIVE breadcrumb(id, reply_to_post_number) AS ( post_ids = DB.query_single(<<~SQL, post_id: id, topic_id: topic_id)
SELECT p.id, p.reply_to_post_number FROM posts AS p WITH RECURSIVE breadcrumb(id, reply_to_post_number) AS (
WHERE p.id = :post_id SELECT p.id, p.reply_to_post_number FROM posts AS p
UNION WHERE p.id = :post_id
SELECT p.id, p.reply_to_post_number FROM posts AS p, breadcrumb UNION
WHERE breadcrumb.reply_to_post_number = p.post_number SELECT p.id, p.reply_to_post_number FROM posts AS p, breadcrumb
AND p.topic_id = :topic_id WHERE breadcrumb.reply_to_post_number = p.post_number
) SELECT id from breadcrumb ORDER by id", post_id: id, topic_id: topic_id).to_a AND p.topic_id = :topic_id
)
post_ids.map! { |r| r['id'].to_i } SELECT id from breadcrumb
.reject! { |post_id| post_id == id } WHERE id <> :post_id
ORDER by id
SQL
# [1,2,3][-10,-1] => nil # [1,2,3][-10,-1] => nil
post_ids = (post_ids[(0 - max_replies)..-1] || post_ids) 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) def self.rebake_all_quoted_posts(user_id)
return if user_id.blank? return if user_id.blank?
Post.exec_sql <<-SQL DB.exec(<<~SQL, user_id)
WITH user_quoted_posts AS ( WITH user_quoted_posts AS (
SELECT post_id SELECT post_id
FROM quoted_posts 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 UPDATE posts
SET baked_version = NULL SET baked_version = NULL

View File

@ -340,7 +340,7 @@ SQL
def self.copy(original_post, target_post) def self.copy(original_post, target_post)
cols_to_copy = (column_names - %w{id post_id}).join(', ') 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}) INSERT INTO post_actions(post_id, #{cols_to_copy})
SELECT #{target_post.id}, #{cols_to_copy} SELECT #{target_post.id}, #{cols_to_copy}
FROM post_actions FROM post_actions
@ -425,26 +425,29 @@ SQL
# Returns the flag counts for a post, taking into account that some users # Returns the flag counts for a post, taking into account that some users
# can weigh flags differently. # can weigh flags differently.
def self.flag_counts_for(post_id) def self.flag_counts_for(post_id)
flag_counts = exec_sql("SELECT SUM(CASE params = {
WHEN pa.disagreed_at IS NULL AND pa.staff_took_action THEN :flags_required_to_hide_post post_id: post_id,
WHEN pa.disagreed_at IS NULL AND NOT pa.staff_took_action THEN 1 post_action_types: PostActionType.auto_action_flag_types.values,
ELSE 0 flags_required_to_hide_post: SiteSetting.flags_required_to_hide_post
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
[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 end
def post_action_type_key def post_action_type_key

View File

@ -10,7 +10,7 @@ class PostRevision < ActiveRecord::Base
def self.ensure_consistency! def self.ensure_consistency!
# 1 - fix the numbers # 1 - fix the numbers
PostRevision.exec_sql <<-SQL DB.exec <<-SQL
UPDATE post_revisions UPDATE post_revisions
SET number = pr.rank 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 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 SQL
# 2 - fix the versions on the posts # 2 - fix the versions on the posts
PostRevision.exec_sql <<-SQL DB.exec <<-SQL
UPDATE posts UPDATE posts
SET version = 1 + (SELECT COUNT(*) FROM post_revisions WHERE post_id = posts.id), 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') public_version = 1 + (SELECT COUNT(*) FROM post_revisions pr WHERE post_id = posts.id AND pr.hidden = 'f')

View File

@ -12,7 +12,7 @@ class PostTiming < ActiveRecord::Base
def self.pretend_read(topic_id, actual_read_post_number, pretend_read_post_number) 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 # 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 SELECT :topic_id, user_id, :pretend_read_post_number, 1
FROM post_timings pt FROM post_timings pt
WHERE topic_id = :topic_id AND WHERE topic_id = :topic_id AND
@ -34,7 +34,7 @@ class PostTiming < ActiveRecord::Base
def self.record_new_timing(args) def self.record_new_timing(args)
begin 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 SELECT :topic_id, :user_id, :post_number, :msecs
WHERE NOT EXISTS(SELECT 1 FROM post_timings WHERE NOT EXISTS(SELECT 1 FROM post_timings
WHERE topic_id = :topic_id WHERE topic_id = :topic_id
@ -53,12 +53,13 @@ class PostTiming < ActiveRecord::Base
# Increases a timer if a row exists, otherwise create it # Increases a timer if a row exists, otherwise create it
def self.record_timing(args) def self.record_timing(args)
rows = exec_sql_row_count("UPDATE post_timings rows = DB.exec(<<~SQL, args)
SET msecs = msecs + :msecs UPDATE post_timings
WHERE topic_id = :topic_id SET msecs = msecs + :msecs
AND user_id = :user_id WHERE topic_id = :topic_id
AND post_number = :post_number", AND user_id = :user_id
args) AND post_number = :post_number
SQL
record_new_timing(args) if rows == 0 record_new_timing(args) if rows == 0
end end
@ -115,9 +116,7 @@ class PostTiming < ActiveRecord::Base
RETURNING x.idx RETURNING x.idx
SQL SQL
result = exec_sql(sql) existing = Set.new(DB.query_single(sql))
result.type_map = SqlBuilder.pg_type_map
existing = Set.new(result.column_values(0))
sql = <<~SQL sql = <<~SQL
SELECT 1 FROM topics SELECT 1 FROM topics
@ -126,7 +125,7 @@ SQL
id = :topic_id id = :topic_id
SQL 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 new_posts_read = timings.size - existing.size if is_regular
timings.each_with_index do |(post_number, time), index| timings.each_with_index do |(post_number, time), index|

View File

@ -11,7 +11,7 @@ class QuotedPost < ActiveRecord::Base
uniq = {} 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| doc.css("aside.quote[data-topic]").each do |a|
topic_id = a['data-topic'].to_i topic_id = a['data-topic'].to_i
@ -23,7 +23,7 @@ class QuotedPost < ActiveRecord::Base
begin begin
# It would be so much nicer if we used post_id in quotes # 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) INSERT INTO quoted_posts (post_id, quoted_post_id, created_at, updated_at)
SELECT :post_id, p.id, current_timestamp, current_timestamp SELECT :post_id, p.id, current_timestamp, current_timestamp
FROM posts p FROM posts p

View File

@ -94,8 +94,8 @@ class ScreenedIpAddress < ActiveRecord::Base
end end
def self.star_subnets_query def self.star_subnets_query
@star_subnets_query ||= <<-SQL @star_subnets_query ||= <<~SQL
SELECT network(inet(host(ip_address) || '/24')) AS ip_range SELECT network(inet(host(ip_address) || '/24'))::text AS ip_range
FROM screened_ip_addresses FROM screened_ip_addresses
WHERE action_type = #{ScreenedIpAddress.actions[:block]} WHERE action_type = #{ScreenedIpAddress.actions[:block]}
AND family(ip_address) = 4 AND family(ip_address) = 4
@ -106,9 +106,9 @@ class ScreenedIpAddress < ActiveRecord::Base
end end
def self.star_star_subnets_query def self.star_star_subnets_query
@star_star_subnets_query ||= <<-SQL @star_star_subnets_query ||= <<~SQL
WITH weighted_subnets AS ( 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) CASE masklen(ip_address)
WHEN 32 THEN 1 WHEN 32 THEN 1
WHEN 24 THEN :roll_up_weight WHEN 24 THEN :roll_up_weight
@ -127,12 +127,12 @@ class ScreenedIpAddress < ActiveRecord::Base
def self.star_subnets def self.star_subnets
min_count = SiteSetting.min_ban_entries_for_roll_up 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 end
def self.star_star_subnets def self.star_star_subnets
weight = SiteSetting.min_ban_entries_for_roll_up 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 end
def self.roll_up(current_user = Discourse.system_user) def self.roll_up(current_user = Discourse.system_user)
@ -143,7 +143,7 @@ class ScreenedIpAddress < ActiveRecord::Base
subnets.each do |subnet| subnets.each do |subnet|
ScreenedIpAddress.create(ip_address: subnet) unless ScreenedIpAddress.where("? <<= ip_address", subnet).exists? ScreenedIpAddress.create(ip_address: subnet) unless ScreenedIpAddress.where("? <<= ip_address", subnet).exists?
sql = <<-SQL sql = <<~SQL
UPDATE screened_ip_addresses UPDATE screened_ip_addresses
SET match_count = sum_match_count SET match_count = sum_match_count
, created_at = min_created_at , created_at = min_created_at
@ -160,7 +160,7 @@ class ScreenedIpAddress < ActiveRecord::Base
WHERE ip_address = :ip_address WHERE ip_address = :ip_address
SQL SQL
ScreenedIpAddress.exec_sql(sql, ip_address: subnet) DB.exec(sql, ip_address: subnet)
ScreenedIpAddress.where(action_type: ScreenedIpAddress.actions[:block]) ScreenedIpAddress.where(action_type: ScreenedIpAddress.actions[:block])
.where("family(ip_address) = 4") .where("family(ip_address) = 4")

View File

@ -24,7 +24,7 @@ class StylesheetCache < ActiveRecord::Base
.pluck(:id) .pluck(:id)
.last .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 end
success success

View File

@ -25,7 +25,7 @@ class Tag < ActiveRecord::Base
end end
def self.update_topic_counts def self.update_topic_counts
Tag.exec_sql <<~SQL DB.exec <<~SQL
UPDATE tags t UPDATE tags t
SET topic_count = x.topic_count SET topic_count = x.topic_count
FROM ( FROM (
@ -41,7 +41,7 @@ class Tag < ActiveRecord::Base
AND x.topic_count <> t.topic_count AND x.topic_count <> t.topic_count
SQL SQL
Tag.exec_sql <<~SQL DB.exec <<~SQL
UPDATE tags t UPDATE tags t
SET pm_topic_count = x.pm_topic_count SET pm_topic_count = x.pm_topic_count
FROM ( 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})" 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 SELECT tags.name as tag_name, SUM(stats.topic_count) AS sum_topic_count
FROM category_tag_stats stats FROM category_tag_stats stats
JOIN tags ON stats.tag_id = tags.id AND stats.topic_count > 0 JOIN tags ON stats.tag_id = tags.id AND stats.topic_count > 0
@ -81,7 +81,7 @@ class Tag < ActiveRecord::Base
LIMIT #{limit} LIMIT #{limit}
SQL SQL
tag_names_with_counts.map { |row| row['tag_name'] } tag_names_with_counts.map { |row| row.tag_name }
end end
def self.pm_tags(limit_arg: nil, guardian: nil, allowed_user: nil) 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 limit = limit_arg || SiteSetting.max_tags_in_filter_list
user_id = allowed_user.id user_id = allowed_user.id
tag_names_with_counts = Tag.exec_sql <<~SQL DB.query_hash(<<~SQL).map!(&:symbolize_keys!)
SELECT tags.name, COUNT(topics.id) AS topic_count SELECT tags.name as id, tags.name as text, COUNT(topics.id) AS count
FROM tags FROM tags
JOIN topic_tags ON tags.id = topic_tags.tag_id JOIN topic_tags ON tags.id = topic_tags.tag_id
JOIN topics ON topics.id = topic_tags.topic_id JOIN topics ON topics.id = topic_tags.topic_id
@ -109,8 +109,6 @@ class Tag < ActiveRecord::Base
GROUP BY tags.name GROUP BY tags.name
LIMIT #{limit} LIMIT #{limit}
SQL SQL
tag_names_with_counts.map { |t| { id: t['name'], text: t['name'], count: t['topic_count'] } }
end end
def self.include_tags? def self.include_tags?

View File

@ -59,7 +59,7 @@ class TopTopic < ActiveRecord::Base
end end
def self.remove_invisible_topics 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 SELECT COALESCE(topic_id, 0) AS id FROM categories
), invisible_topic_ids AS ( ), invisible_topic_ids AS (
SELECT id SELECT id
@ -76,7 +76,7 @@ class TopTopic < ActiveRecord::Base
end end
def self.add_new_visible_topics 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 SELECT COALESCE(topic_id, 0) AS id FROM categories
), visible_topics AS ( ), visible_topics AS (
SELECT t.id SELECT t.id
@ -167,7 +167,7 @@ class TopTopic < ActiveRecord::Base
time_filter = "topics.created_at < :from" time_filter = "topics.created_at < :from"
end end
sql = <<-SQL sql = <<~SQL
WITH top AS ( WITH top AS (
SELECT CASE SELECT CASE
WHEN #{time_filter} THEN 0 WHEN #{time_filter} THEN 0
@ -197,7 +197,7 @@ class TopTopic < ActiveRecord::Base
AND #{period}_score <> top.score AND #{period}_score <> top.score
SQL SQL
exec_sql(sql, from: start_of(period)) DB.exec(sql, from: start_of(period))
end end
def self.start_of(period) def self.start_of(period)
@ -211,7 +211,7 @@ class TopTopic < ActiveRecord::Base
end end
def self.update_top_topics(period, sort, inner_join) 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 SET #{period}_#{sort}_count = c.count
FROM top_topics tt FROM top_topics tt
INNER JOIN (#{inner_join}) c ON tt.topic_id = c.topic_id INNER JOIN (#{inner_join}) c ON tt.topic_id = c.topic_id

View File

@ -352,7 +352,7 @@ class Topic < ActiveRecord::Base
if !new_record? && !Discourse.readonly_mode? if !new_record? && !Discourse.readonly_mode?
# make sure data is set in table, this also allows us to change algorithm # make sure data is set in table, this also allows us to change algorithm
# by simply nulling this column # 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
end end
@ -522,130 +522,140 @@ class Topic < ActiveRecord::Base
# Atomically creates the next post number # Atomically creates the next post number
def self.next_post_number(topic_id, reply = false, whisper = false) 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 if whisper
result = exec_sql("UPDATE topics result = DB.query_single(<<~SQL, highest, topic_id)
SET highest_staff_post_number = ? + 1 UPDATE topics
WHERE id = ? SET highest_staff_post_number = ? + 1
RETURNING highest_staff_post_number", highest, topic_id) WHERE id = ?
RETURNING highest_staff_post_number
SQL
result.first['highest_staff_post_number'].to_i result.first.to_i
else else
reply_sql = reply ? ", reply_count = reply_count + 1" : "" reply_sql = reply ? ", reply_count = reply_count + 1" : ""
result = exec_sql("UPDATE topics result = DB.query_single(<<~SQL, highest: highest, topic_id: topic_id)
SET highest_staff_post_number = :highest + 1, UPDATE topics
highest_post_number = :highest + 1#{reply_sql}, SET highest_staff_post_number = :highest + 1,
posts_count = posts_count + 1 highest_post_number = :highest + 1#{reply_sql},
WHERE id = :topic_id posts_count = posts_count + 1
RETURNING highest_post_number", highest: highest, topic_id: topic_id) WHERE id = :topic_id
RETURNING highest_post_number
SQL
result.first['highest_post_number'].to_i result.first.to_i
end end
end end
def self.reset_all_highest! def self.reset_all_highest!
exec_sql <<SQL DB.exec <<~SQL
WITH WITH
X as ( X as (
SELECT topic_id, SELECT topic_id,
COALESCE(MAX(post_number), 0) highest_post_number COALESCE(MAX(post_number), 0) highest_post_number
FROM posts FROM posts
WHERE deleted_at IS NULL WHERE deleted_at IS NULL
GROUP BY topic_id GROUP BY topic_id
), ),
Y as ( Y as (
SELECT topic_id, SELECT topic_id,
coalesce(MAX(post_number), 0) highest_post_number, coalesce(MAX(post_number), 0) highest_post_number,
count(*) posts_count, count(*) posts_count,
max(created_at) last_posted_at max(created_at) last_posted_at
FROM posts FROM posts
WHERE deleted_at IS NULL AND post_type <> 4 WHERE deleted_at IS NULL AND post_type <> 4
GROUP BY topic_id GROUP BY topic_id
) )
UPDATE topics UPDATE topics
SET SET
highest_staff_post_number = X.highest_post_number, highest_staff_post_number = X.highest_post_number,
highest_post_number = Y.highest_post_number, highest_post_number = Y.highest_post_number,
last_posted_at = Y.last_posted_at, last_posted_at = Y.last_posted_at,
posts_count = Y.posts_count posts_count = Y.posts_count
FROM X, Y FROM X, Y
WHERE WHERE
X.topic_id = topics.id AND X.topic_id = topics.id AND
Y.topic_id = topics.id AND ( Y.topic_id = topics.id AND (
topics.highest_staff_post_number <> X.highest_post_number OR topics.highest_staff_post_number <> X.highest_post_number OR
topics.highest_post_number <> Y.highest_post_number OR topics.highest_post_number <> Y.highest_post_number OR
topics.last_posted_at <> Y.last_posted_at OR topics.last_posted_at <> Y.last_posted_at OR
topics.posts_count <> Y.posts_count topics.posts_count <> Y.posts_count
) )
SQL SQL
end end
# If a post is deleted we have to update our highest post counters # If a post is deleted we have to update our highest post counters
def self.reset_highest(topic_id) def self.reset_highest(topic_id)
result = exec_sql "UPDATE topics result = DB.query_single(<<~SQL, topic_id: topic_id)
SET UPDATE topics
highest_staff_post_number = ( SET
SELECT COALESCE(MAX(post_number), 0) FROM posts highest_staff_post_number = (
WHERE topic_id = :topic_id AND SELECT COALESCE(MAX(post_number), 0) FROM posts
deleted_at IS NULL WHERE topic_id = :topic_id AND
), deleted_at IS NULL
highest_post_number = ( ),
SELECT COALESCE(MAX(post_number), 0) FROM posts highest_post_number = (
WHERE topic_id = :topic_id AND SELECT COALESCE(MAX(post_number), 0) FROM posts
deleted_at IS NULL AND WHERE topic_id = :topic_id AND
post_type <> 4 deleted_at IS NULL AND
), post_type <> 4
posts_count = ( ),
SELECT count(*) FROM posts posts_count = (
WHERE deleted_at IS NULL AND SELECT count(*) FROM posts
topic_id = :topic_id AND WHERE deleted_at IS NULL AND
post_type <> 4 topic_id = :topic_id AND
), post_type <> 4
),
last_posted_at = ( last_posted_at = (
SELECT MAX(created_at) FROM posts SELECT MAX(created_at) FROM posts
WHERE topic_id = :topic_id AND WHERE topic_id = :topic_id AND
deleted_at IS NULL AND deleted_at IS NULL AND
post_type <> 4 post_type <> 4
) )
WHERE id = :topic_id WHERE id = :topic_id
RETURNING highest_post_number", topic_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 # Update the forum topic user records
exec_sql "UPDATE topic_users DB.exec(<<~SQL, highest: highest_post_number, topic_id: topic_id)
SET last_read_post_number = CASE UPDATE topic_users
WHEN last_read_post_number > :highest THEN :highest SET last_read_post_number = CASE
ELSE last_read_post_number WHEN last_read_post_number > :highest THEN :highest
END, ELSE last_read_post_number
highest_seen_post_number = CASE END,
WHEN highest_seen_post_number > :highest THEN :highest highest_seen_post_number = CASE
ELSE highest_seen_post_number WHEN highest_seen_post_number > :highest THEN :highest
END ELSE highest_seen_post_number
WHERE topic_id = :topic_id", END
highest: highest_post_number, WHERE topic_id = :topic_id
topic_id: topic_id SQL
end end
# This calculates the geometric mean of the posts and stores it with the topic # This calculates the geometric mean of the posts and stores it with the topic
def self.calculate_avg_time(min_topic_age = nil) def self.calculate_avg_time(min_topic_age = nil)
builder = SqlBuilder.new("UPDATE topics builder = DB.build <<~SQL
SET avg_time = x.gmean UPDATE topics
FROM (SELECT topic_id, SET avg_time = x.gmean
round(exp(avg(ln(avg_time)))) AS gmean FROM (SELECT topic_id,
FROM posts round(exp(avg(ln(avg_time)))) AS gmean
WHERE avg_time > 0 AND avg_time IS NOT NULL FROM posts
GROUP BY topic_id) AS x WHERE avg_time > 0 AND avg_time IS NOT NULL
/*where*/") GROUP BY topic_id) AS x
/*where*/
SQL
builder.where("x.topic_id = topics.id AND builder.where <<~SQL
(topics.avg_time <> x.gmean OR topics.avg_time IS NULL)") x.topic_id = topics.id AND
(topics.avg_time <> x.gmean OR topics.avg_time IS NULL)
SQL
if min_topic_age if min_topic_age
builder.where("topics.bumped_at > :bumped_at", bumped_at: 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 # OR if you have it archived as a user explicitly
sql = <<~SQL sql = <<~SQL
SELECT 1 SELECT 1
WHERE WHERE
( (
SELECT count(*) FROM topic_allowed_groups tg SELECT count(*) FROM topic_allowed_groups tg
JOIN group_archived_messages gm JOIN group_archived_messages gm
ON gm.topic_id = tg.topic_id AND ON gm.topic_id = tg.topic_id AND
gm.group_id = tg.group_id 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) 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 AND tg.topic_id = :topic_id
) = ) =
( (
SELECT case when count(*) = 0 then -1 else count(*) end FROM topic_allowed_groups tg 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) 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 AND tg.topic_id = :topic_id
) )
UNION ALL UNION ALL
SELECT 1 FROM topic_allowed_users tu 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 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 WHERE tu.user_id = :user_id AND tu.topic_id = :topic_id
SQL 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 end
TIME_TO_FIRST_RESPONSE_SQL ||= <<-SQL TIME_TO_FIRST_RESPONSE_SQL ||= <<-SQL
@ -1325,8 +1335,8 @@ SQL
) = 1 ) = 1
SQL SQL
result = Topic.exec_sql(sql, private_message: Archetype.private_message, topic_id: self.id) result = DB.exec(sql, private_message: Archetype.private_message, topic_id: self.id)
result.ntuples != 0 result != 0
end end
def featured_link_root_domain def featured_link_root_domain

View File

@ -79,7 +79,7 @@ WHERE tt.id = tt2.id AND
#{filter2} #{filter2}
SQL SQL
Topic.exec_sql(sql) DB.exec(sql)
end end
private private

View File

@ -38,7 +38,7 @@ class TopicLink < ActiveRecord::Base
def self.topic_map(guardian, topic_id) def self.topic_map(guardian, topic_id)
# Sam: complicated reports are really hard in AR # Sam: complicated reports are really hard in AR
builder = SqlBuilder.new <<-SQL builder = DB.build <<-SQL
SELECT ftl.url, SELECT ftl.url,
COALESCE(ft.title, ftl.title) AS title, COALESCE(ft.title, ftl.title) AS title,
ftl.link_topic_id, ftl.link_topic_id,
@ -64,16 +64,16 @@ SQL
builder.secure_category(guardian.secure_category_ids) builder.secure_category(guardian.secure_category_ids)
builder.exec.to_a builder.query
end end
def self.counts_for(guardian, topic, posts) def self.counts_for(guardian, topic, posts)
return {} if posts.blank? return {} if posts.blank?
# Sam: I don't know how to write this cleanly in AR, # Sam: this is not tidy in AR and also happens to be a critical path
# in particular the securing logic is tricky and would fallback to SQL anyway # for topic view
builder = SqlBuilder.new("SELECT builder = DB.build("SELECT
l.post_id, l.post_id,
l.url, l.url,
l.clicks, l.clicks,
@ -91,10 +91,11 @@ SQL
builder.where("COALESCE(t.archetype, 'regular') <> :archetype", archetype: Archetype.private_message) builder.where("COALESCE(t.archetype, 'regular') <> :archetype", archetype: Archetype.private_message)
# not certain if pluck is right, cause it may interfere with caching # 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.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] ||= []
result[l.post_id] << { url: l.url, result[l.post_id] << { url: l.url,
clicks: l.clicks, clicks: l.clicks,
@ -102,6 +103,7 @@ SQL
internal: l.internal, internal: l.internal,
reflection: l.reflection } reflection: l.reflection }
end end
result
end end
def self.extract_from(post) def self.extract_from(post)

View File

@ -53,26 +53,26 @@ class TopicUser < ActiveRecord::Base
def unwatch_categories!(user, category_ids) def unwatch_categories!(user, category_ids)
track_threshold = user.user_option.auto_track_topics_after_msecs track_threshold = user.user_option.auto_track_topics_after_msecs
sql = <<SQL sql = <<~SQL
UPDATE topic_users tu UPDATE topic_users tu
SET notification_level = CASE SET notification_level = CASE
WHEN t.user_id = :user_id THEN :watching WHEN t.user_id = :user_id THEN :watching
WHEN total_msecs_viewed > :track_threshold AND :track_threshold >= 0 THEN :tracking WHEN total_msecs_viewed > :track_threshold AND :track_threshold >= 0 THEN :tracking
ELSE :regular ELSE :regular
end end
FROM topics t 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 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
exec_sql(sql, DB.exec(sql,
watching: notification_levels[:watching], watching: notification_levels[:watching],
tracking: notification_levels[:tracking], tracking: notification_levels[:tracking],
regular: notification_levels[:regular], regular: notification_levels[:regular],
muted: notification_levels[:muted], muted: notification_levels[:muted],
category_ids: category_ids, category_ids: category_ids,
user_id: user.id, user_id: user.id,
track_threshold: track_threshold track_threshold: track_threshold
) )
end end
# Find the information specific to a user in a forum topic # Find the information specific to a user in a forum topic
@ -296,16 +296,15 @@ SQL
# 86400000 = 1 day # 86400000 = 1 day
rows = rows =
if user.staff? if user.staff?
exec_sql(UPDATE_TOPIC_USER_SQL_STAFF, args).values DB.query(UPDATE_TOPIC_USER_SQL_STAFF, args)
else else
exec_sql(UPDATE_TOPIC_USER_SQL, args).values DB.query(UPDATE_TOPIC_USER_SQL, args)
end end
if rows.length == 1 if rows.length == 1
before = rows[0][1].to_i before = rows[0].old_level.to_i
after = rows[0][0].to_i after = rows[0].notification_level.to_i
before_last_read = rows[0].last_read_post_number.to_i
before_last_read = rows[0][2].to_i
if before_last_read < post_number if before_last_read < post_number
# The user read at least one new post # The user read at least one new post
@ -333,9 +332,9 @@ SQL
begin begin
if user.staff? if user.staff?
exec_sql(INSERT_TOPIC_USER_SQL_STAFF, args) DB.exec(INSERT_TOPIC_USER_SQL_STAFF, args)
else else
exec_sql(INSERT_TOPIC_USER_SQL, args) DB.exec(INSERT_TOPIC_USER_SQL, args)
end end
rescue PG::UniqueViolation rescue PG::UniqueViolation
# if record is inserted between two statements this can happen # if record is inserted between two statements this can happen
@ -431,7 +430,7 @@ SQL
) )
SQL SQL
TopicUser.exec_sql(sql, user_id: user_id, count: count) DB.exec(sql, user_id: user_id, count: count)
end end
def self.ensure_consistency!(topic_id = nil) def self.ensure_consistency!(topic_id = nil)

View File

@ -122,7 +122,7 @@ class TrustLevel3Requirements
AND uh.action IN (:silence_user, :unsilence_user, :suspend_user, :unsuspend_user) AND uh.action IN (:silence_user, :unsilence_user, :suspend_user, :unsuspend_user)
SQL SQL
PenaltyCounts.new(UserHistory.exec_sql(sql, args).first) PenaltyCounts.new(DB.query_hash(sql, args).first)
end end
def min_days_visited def min_days_visited

View File

@ -208,7 +208,7 @@ class User < ActiveRecord::Base
def self.username_available?(username, email = nil) def self.username_available?(username, email = nil)
lower = username.downcase lower = username.downcase
return false if reserved_username?(lower) 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 # 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 }) 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 AND NOT read
SQL 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 end
def unread_private_messages def unread_private_messages
@ -408,11 +409,11 @@ class User < ActiveRecord::Base
AND NOT read AND NOT read
SQL SQL
User.exec_sql(sql, DB.query_single(sql,
user_id: id, user_id: id,
seen_notification_id: seen_notification_id, seen_notification_id: seen_notification_id,
pm: Notification.types[:private_message] pm: Notification.types[:private_message]
).getvalue(0, 0).to_i )[0].to_i
end end
end end
@ -446,7 +447,7 @@ class User < ActiveRecord::Base
notification = notifications.visible.order('notifications.id desc').first notification = notifications.visible.order('notifications.id desc').first
json = NotificationSerializer.new(notification).as_json if notification json = NotificationSerializer.new(notification).as_json if notification
sql = " sql = (<<~SQL).freeze
SELECT * FROM ( SELECT * FROM (
SELECT n.id, n.read FROM notifications n SELECT n.id, n.read FROM notifications n
LEFT JOIN topics t ON n.topic_id = t.id LEFT JOIN topics t ON n.topic_id = t.id
@ -469,13 +470,13 @@ class User < ActiveRecord::Base
ORDER BY n.id DESC ORDER BY n.id DESC
LIMIT 20 LIMIT 20
) AS y ) AS y
" SQL
recent = User.exec_sql(sql, recent = DB.query(sql,
user_id: id, user_id: id,
type: Notification.types[:private_message] type: Notification.types[:private_message]
).values.map! do |id, read| ).map! do |r|
[id.to_i, read] [r.id, r.read]
end end
payload = { payload = {
@ -1155,12 +1156,12 @@ class User < ActiveRecord::Base
end end
USERNAME_EXISTS_SQL = <<~SQL 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) WHERE users.username_lower = :username)
UNION ALL UNION ALL
(SELECT groups.id AS group_id FROM groups (SELECT groups.id, false as is_user FROM groups
WHERE lower(groups.name) = :username) WHERE lower(groups.name) = :username)
SQL SQL
@ -1168,11 +1169,14 @@ class User < ActiveRecord::Base
username_format_validator || begin username_format_validator || begin
lower = username.downcase lower = username.downcase
existing = User.exec_sql( existing = DB.query(
USERNAME_EXISTS_SQL, username: lower 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')) errors.add(:username, I18n.t(:'user.username.unique'))
end end
end end
@ -1200,7 +1204,7 @@ class User < ActiveRecord::Base
end end
if values.present? 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
end end

View File

@ -97,7 +97,8 @@ SQL
AND t.id IN (SELECT topic_id FROM topic_allowed_users WHERE user_id = :user_id) AND t.id IN (SELECT topic_id FROM topic_allowed_users WHERE user_id = :user_id)
SQL 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 sql = <<-SQL
SELECT g.name, COUNT(*) "count" SELECT g.name, COUNT(*) "count"
@ -112,8 +113,8 @@ SQL
result = { all: all, mine: mine, unread: unread } result = { all: all, mine: mine, unread: unread }
exec_sql(sql, user_id: user_id).each do |row| DB.query(sql, user_id: user_id).each do |row|
(result[:groups] ||= []) << { name: row["name"], count: row["count"].to_i } (result[:groups] ||= []) << { name: row.name, count: row.count.to_i }
end end
result result

View File

@ -131,7 +131,7 @@ class UserAuthToken < ActiveRecord::Base
token = SecureRandom.hex(16) token = SecureRandom.hex(16)
result = UserAuthToken.exec_sql(" result = DB.exec("
UPDATE user_auth_tokens UPDATE user_auth_tokens
SET SET
auth_token_seen = false, auth_token_seen = false,
@ -150,7 +150,7 @@ class UserAuthToken < ActiveRecord::Base
safeguard_time: 30.seconds.ago safeguard_time: 30.seconds.ago
) )
if result.cmdtuples > 0 if result > 0
reload reload
self.unhashed_auth_token = token self.unhashed_auth_token = token

View File

@ -6,10 +6,14 @@ class UserOption < ActiveRecord::Base
after_save :update_tracked_topics after_save :update_tracked_topics
def self.ensure_consistency! def self.ensure_consistency!
exec_sql("SELECT u.id FROM users u sql = <<~SQL
LEFT JOIN user_options o ON o.user_id = u.id SELECT u.id FROM users u
WHERE o.user_id IS NULL").values.each do |id, _| LEFT JOIN user_options o ON o.user_id = u.id
UserOption.create(user_id: id.to_i) WHERE o.user_id IS NULL
SQL
DB.query_single(sql).each do |id|
UserOption.create(user_id: id)
end end
end end

View File

@ -23,34 +23,36 @@ class UserStat < ActiveRecord::Base
# we also ensure we only touch the table if data changes # we also ensure we only touch the table if data changes
# Update denormalized topics_entered # Update denormalized topics_entered
exec_sql "UPDATE user_stats SET topics_entered = X.c DB.exec(<<~SQL, seen_at: last_seen)
FROM UPDATE user_stats SET topics_entered = X.c
(SELECT v.user_id, COUNT(topic_id) AS c FROM
FROM topic_views AS v (SELECT v.user_id, COUNT(topic_id) AS c
WHERE v.user_id IN ( FROM topic_views AS v
SELECT u1.id FROM users u1 where u1.last_seen_at > :seen_at 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 GROUP BY v.user_id) AS X
X.user_id = user_stats.user_id AND WHERE
X.c <> topics_entered X.user_id = user_stats.user_id AND
", seen_at: last_seen X.c <> topics_entered
SQL
# Update denormalzied posts_read_count # Update denormalzied posts_read_count
exec_sql "UPDATE user_stats SET posts_read_count = X.c DB.exec(<<~SQL, seen_at: last_seen)
FROM UPDATE user_stats SET posts_read_count = X.c
(SELECT pt.user_id, FROM
COUNT(*) AS c (SELECT pt.user_id,
FROM users AS u COUNT(*) AS c
JOIN post_timings AS pt ON pt.user_id = u.id FROM users AS u
JOIN topics t ON t.id = pt.topic_id JOIN post_timings AS pt ON pt.user_id = u.id
WHERE u.last_seen_at > :seen_at AND JOIN topics t ON t.id = pt.topic_id
t.archetype = 'regular' AND WHERE u.last_seen_at > :seen_at AND
t.deleted_at IS NULL t.archetype = 'regular' AND
GROUP BY pt.user_id) AS X t.deleted_at IS NULL
WHERE X.user_id = user_stats.user_id AND GROUP BY pt.user_id) AS X
X.c <> posts_read_count WHERE X.user_id = user_stats.user_id AND
", seen_at: last_seen X.c <> posts_read_count
SQL
end end
# topic_reply_count is a count of posts in other users' topics # topic_reply_count is a count of posts in other users' topics

View File

@ -11,7 +11,7 @@ class UserVisit < ActiveRecord::Base
end end
def self.count_by_active_users(start_date, end_date) def self.count_by_active_users(start_date, end_date)
sql = <<SQL sql = <<~SQL
WITH dau AS ( WITH dau AS (
SELECT date_trunc('day', user_visits.visited_at)::DATE AS date, SELECT date_trunc('day', user_visits.visited_at)::DATE AS date,
count(distinct user_visits.user_id) AS dau count(distinct user_visits.user_id) AS dau
@ -27,9 +27,9 @@ class UserVisit < ActiveRecord::Base
WHERE user_visits.visited_at::DATE BETWEEN dau.date - 29 AND dau.date WHERE user_visits.visited_at::DATE BETWEEN dau.date - 29 AND dau.date
) AS mau ) AS mau
FROM dau FROM dau
SQL SQL
UserVisit.exec_sql(sql, start_date: start_date, end_date: end_date).to_a DB.query_hash(sql, start_date: start_date, end_date: end_date)
end end
# A count of visits in a date range by day # A count of visits in a date range by day
@ -42,16 +42,16 @@ SQL
end end
def self.ensure_consistency! def self.ensure_consistency!
exec_sql <<SQL DB.exec <<~SQL
UPDATE user_stats u set days_visited = UPDATE user_stats u set days_visited =
( (
SELECT COUNT(*) FROM user_visits v WHERE v.user_id = u.user_id SELECT COUNT(*) FROM user_visits v WHERE v.user_id = u.user_id
) )
WHERE days_visited <> WHERE days_visited <>
( (
SELECT COUNT(*) FROM user_visits v WHERE v.user_id = u.user_id SELECT COUNT(*) FROM user_visits v WHERE v.user_id = u.user_id
) )
SQL SQL
end end
end end

View File

@ -2,7 +2,7 @@ class TopicLinkSerializer < ApplicationSerializer
attributes :url, attributes :url,
:title, :title,
:fancy_title, # :fancy_title,
:internal, :internal,
:attachment, :attachment,
:reflection, :reflection,
@ -11,44 +11,12 @@ class TopicLinkSerializer < ApplicationSerializer
:domain, :domain,
:root_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 def attachment
Discourse.store.has_been_uploaded?(object['url']) 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
end end
def include_user_id? def include_user_id?
object['user_id'].present? object.user_id.present?
end
def domain
object['domain']
end end
def root_domain def root_domain

View File

@ -204,17 +204,18 @@ class BadgeGranter
end end
query_plan = nil query_plan = nil
# HACK: active record is weird, force it to go down the sanitization path that cares not for % stuff # HACK: active record sanitization too flexible, 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] # 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| sample.each do |result|
raise "Query returned a non-existent user ID:\n#{result[:id]}" unless User.find(result[:id]).present? 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] raise "Query did not return a badge grant time\n(Try using 'current_timestamp granted_at')" unless result.granted_at
if opts[:target_posts] if opts[:target_posts]
raise "Query did not return a post ID" unless result[:post_id] 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 returned a non-existent post ID:\n#{result.post_id}" unless Post.exists?(result.post_id).present?
end end
end end
@ -258,28 +259,31 @@ class BadgeGranter
WHERE ub.badge_id = :id AND q.user_id IS NULL WHERE ub.badge_id = :id AND q.user_id IS NULL
)" )"
Badge.exec_sql(sql, id: badge.id, DB.exec(
post_ids: [-1], sql,
user_ids: [-2], id: badge.id,
backfill: true, post_ids: [-1],
multiple_grant: true # cheat here, cause we only run on backfill and are deleting user_ids: [-2],
) if badge.auto_revoke && full_backfill 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 ( sql = <<~SQL
INSERT INTO user_badges(badge_id, user_id, granted_at, granted_by_id, post_id) WITH w as (
SELECT :id, q.user_id, q.granted_at, -1, #{post_id_field} INSERT INTO user_badges(badge_id, user_id, granted_at, granted_by_id, post_id)
FROM ( #{badge.query} ) q SELECT :id, q.user_id, q.granted_at, -1, #{post_id_field}
LEFT JOIN user_badges ub ON FROM ( #{badge.query} ) q
ub.badge_id = :id AND ub.user_id = q.user_id LEFT JOIN user_badges ub ON
#{post_clause} ub.badge_id = :id AND ub.user_id = q.user_id
/*where*/ #{post_clause}
RETURNING id, user_id, granted_at /*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 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") builder.where("ub.badge_id IS NULL AND q.user_id <> -1")
if (post_ids || user_ids) && !badge.query.include?(":backfill") if (post_ids || user_ids) && !badge.query.include?(":backfill")
@ -297,11 +301,12 @@ class BadgeGranter
return return
end end
builder.map_exec(OpenStruct, id: badge.id, builder.query(
multiple_grant: badge.multiple_grant, id: badge.id,
backfill: full_backfill, multiple_grant: badge.multiple_grant,
post_ids: post_ids || [-2], backfill: full_backfill,
user_ids: user_ids || [-2]).each do |row| post_ids: post_ids || [-2],
user_ids: user_ids || [-2]).each do |row|
# old bronze badges do not matter # old bronze badges do not matter
next if badge.badge_type_id == (BadgeType::Bronze) && row.granted_at < (2.days.ago) next if badge.badge_type_id == (BadgeType::Bronze) && row.granted_at < (2.days.ago)
@ -332,10 +337,11 @@ class BadgeGranter
}.to_json) }.to_json)
end end
Badge.exec_sql("UPDATE user_badges SET notification_id = :notification_id WHERE id = :id", DB.exec(
notification_id: notification.id, "UPDATE user_badges SET notification_id = :notification_id WHERE id = :id",
id: row.id notification_id: notification.id,
) id: row.id
)
end end
badge.reset_grant_count! badge.reset_grant_count!
@ -345,21 +351,22 @@ class BadgeGranter
end end
def self.revoke_ungranted_titles! def self.revoke_ungranted_titles!
Badge.exec_sql("UPDATE users SET title = '' DB.exec <<~SQL
WHERE NOT title IS NULL AND UPDATE users SET title = ''
title <> '' AND WHERE NOT title IS NULL AND
EXISTS ( title <> '' AND
SELECT 1 EXISTS (
FROM user_profiles SELECT 1
WHERE user_id = users.id AND badge_granted_title FROM user_profiles
) AND WHERE user_id = users.id AND badge_granted_title
title NOT IN ( ) AND
SELECT name title NOT IN (
FROM badges SELECT name
WHERE allow_title AND enabled AND FROM badges
badges.id IN (SELECT badge_id FROM user_badges ub where ub.user_id = users.id) 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
end end

View File

@ -194,16 +194,18 @@ class PostAlerter
} }
def group_stats(topic) 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| topic.allowed_groups.map do |g|
{ {
group_id: g.id, group_id: g.id,
group_name: g.name.downcase, group_name: g.name.downcase,
inbox_count: Topic.exec_sql( inbox_count: DB.query_single(sql, group_id: g.id).first.to_i
"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
} }
end end
end end

View File

@ -61,7 +61,7 @@ class SearchIndexer
# Would be nice to use AR here but not sure how to execut Postgres functions # Would be nice to use AR here but not sure how to execut Postgres functions
# when inserting data like this. # when inserting data like this.
rows = Post.exec_sql_row_count(<<~SQL, params) rows = DB.exec(<<~SQL, params)
UPDATE #{table_name} UPDATE #{table_name}
SET SET
raw_data = :raw_data, raw_data = :raw_data,
@ -72,7 +72,7 @@ class SearchIndexer
SQL SQL
if rows == 0 if rows == 0
Post.exec_sql(<<~SQL, params) DB.exec(<<~SQL, params)
INSERT INTO #{table_name} INSERT INTO #{table_name}
(#{foreign_key}, search_data, locale, raw_data, version) (#{foreign_key}, search_data, locale, raw_data, version)
VALUES (:id, #{ranked_index}, :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) def self.queue_post_reindex(topic_id)
return if @disabled return if @disabled
ActiveRecord::Base.exec_sql(<<~SQL, topic_id: topic_id) DB.exec(<<~SQL, topic_id: topic_id)
UPDATE post_search_data UPDATE post_search_data
SET version = 0 SET version = 0
WHERE post_id IN (SELECT id FROM posts WHERE topic_id = :topic_id) WHERE post_id IN (SELECT id FROM posts WHERE topic_id = :topic_id)

View File

@ -89,11 +89,13 @@ class UserMerger
limit_reached = EXCLUDED.limit_reached limit_reached = EXCLUDED.limit_reached
SQL SQL
GivenDailyLike.exec_sql(sql, DB.exec(
source_user_id: @source_user.id, sql,
target_user_id: @target_user.id, source_user_id: @source_user.id,
max_likes_per_day: SiteSetting.max_likes_per_day, target_user_id: @target_user.id,
action_type_id: PostActionType.types[:like]) max_likes_per_day: SiteSetting.max_likes_per_day,
action_type_id: PostActionType.types[:like]
)
end end
def merge_post_timings def merge_post_timings
@ -107,7 +109,7 @@ class UserMerger
AND t.topic_id = s.topic_id AND t.post_number = s.post_number AND t.topic_id = s.topic_id AND t.post_number = s.post_number
SQL 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 end
def merge_user_visits def merge_user_visits
@ -123,7 +125,7 @@ class UserMerger
AND t.visited_at = s.visited_at AND t.visited_at = s.visited_at
SQL 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 end
def update_site_settings def update_site_settings
@ -136,7 +138,7 @@ class UserMerger
def update_user_stats def update_user_stats
# topics_entered # topics_entered
UserStat.exec_sql(<<~SQL, target_user_id: @target_user.id) DB.exec(<<~SQL, target_user_id: @target_user.id)
UPDATE user_stats UPDATE user_stats
SET topics_entered = ( SET topics_entered = (
SELECT COUNT(topic_id) SELECT COUNT(topic_id)
@ -147,7 +149,7 @@ class UserMerger
SQL SQL
# time_read and days_visited # 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 UPDATE user_stats
SET time_read = COALESCE(x.time_read, 0), SET time_read = COALESCE(x.time_read, 0),
days_visited = COALESCE(x.days_visited, 0) days_visited = COALESCE(x.days_visited, 0)
@ -162,7 +164,7 @@ class UserMerger
SQL SQL
# posts_read_count # 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 UPDATE user_stats
SET posts_read_count = ( SET posts_read_count = (
SELECT COUNT(1) SELECT COUNT(1)
@ -176,7 +178,7 @@ class UserMerger
SQL SQL
# likes_given, likes_received, new_since, read_faq, first_post_created_at # 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 UPDATE user_stats AS t
SET likes_given = t.likes_given + s.likes_given, SET likes_given = t.likes_given + s.likes_given,
likes_received = t.likes_received + s.likes_received, likes_received = t.likes_received + s.likes_received,
@ -189,7 +191,7 @@ class UserMerger
end end
def merge_user_attributes 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 UPDATE users AS t
SET created_at = LEAST(t.created_at, s.created_at), SET created_at = LEAST(t.created_at, s.created_at),
updated_at = LEAST(t.updated_at, s.updated_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 WHERE t.id = :target_user_id AND s.id = :source_user_id
SQL 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 UPDATE user_profiles AS t
SET location = COALESCE(t.location, s.location), SET location = COALESCE(t.location, s.location),
website = COALESCE(t.website, s.website), website = COALESCE(t.website, s.website),

View File

@ -143,17 +143,18 @@ class UserUpdater
MutedUser.where('user_id = ? AND muted_user_id not in (?)', user.id, desired_ids).destroy_all 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 # 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) DB.exec(<<~SQL, now: Time.now, user_id: user.id, desired_ids: desired_ids)
SELECT :user_id, id, :now, :now INSERT into muted_users(user_id, muted_user_id, created_at, updated_at)
FROM users SELECT :user_id, id, :now, :now
WHERE FROM users
id in (:desired_ids) AND WHERE
id NOT IN ( id in (:desired_ids) AND
SELECT muted_user_id id NOT IN (
FROM muted_users SELECT muted_user_id
WHERE user_id = :user_id FROM muted_users
)", WHERE user_id = :user_id
now: Time.now, user_id: user.id, desired_ids: desired_ids) )
SQL
end end
end end

View File

@ -0,0 +1,2 @@
require 'mini_sql_multisite_connection'
::DB = MiniSqlMultisiteConnection.instance

View File

@ -288,7 +288,6 @@ Discourse::Application.routes.draw do
get "memory_stats" => "diagnostics#memory_stats", constraints: AdminConstraint.new get "memory_stats" => "diagnostics#memory_stats", constraints: AdminConstraint.new
get "dump_heap" => "diagnostics#dump_heap", 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 end # admin namespace
get "email_preferences" => "email#preferences_redirect", :as => "email_preferences_redirect" get "email_preferences" => "email#preferences_redirect", :as => "email_preferences_redirect"

View File

@ -48,7 +48,7 @@ Migration::ColumnDropper.drop(
}, },
on_drop: ->() { on_drop: ->() {
STDERR.puts "Removing superflous user stats columns!" 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)"
} }
) )

View File

@ -10,18 +10,18 @@ uncat_id = -1 unless Numeric === uncat_id
if uncat_id == -1 || !Category.exists?(uncat_id) if uncat_id == -1 || !Category.exists?(uncat_id)
puts "Seeding uncategorized category!" 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 = '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) (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}' ) VALUES ('#{name}', 'AB9364', 'uncategorized', '', 'FFFFFF', -1, now(), now(), 1, '#{name.downcase}' )
RETURNING id 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'" DB.exec "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 "INSERT INTO site_settings(name, data_type, value, created_at, updated_at)
VALUES ('uncategorized_category_id', 3, #{category_id}, now(), now())" VALUES ('uncategorized_category_id', 3, #{category_id}, now(), now())"
end end

View File

@ -31,7 +31,7 @@ BadgeGrouping.seed do |g|
end end
# BUGFIX # BUGFIX
Badge.exec_sql <<-SQL.squish DB.exec <<-SQL.squish
UPDATE badges UPDATE badges
SET badge_grouping_id = -1 SET badge_grouping_id = -1
WHERE NOT EXISTS ( WHERE NOT EXISTS (

View File

@ -36,7 +36,7 @@ unless Rails.env.test?
end end
# Reset topic count because we don't count the description topic # 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 end
end end

View File

@ -25,7 +25,7 @@ unless Rails.env.test?
end end
# Reset topic count because we don't count the description topic # 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 end
end end

View File

@ -33,7 +33,7 @@ unless Rails.env.test?
end end
# Reset topic count because we don't count the description topic # 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 end
end end

View File

@ -1,7 +1,7 @@
class AddStarredAtToForumThreadUser < ActiveRecord::Migration[4.2] class AddStarredAtToForumThreadUser < ActiveRecord::Migration[4.2]
def up def up
add_column :forum_thread_users, :starred_at, :datetime 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 from
( (
select f1.forum_thread_id, f1.user_id, t.created_at from forum_thread_users f1 select f1.forum_thread_id, f1.user_id, t.created_at from forum_thread_users f1

View File

@ -1,7 +1,7 @@
class MakePostNumberDistinct < ActiveRecord::Migration[4.2] class MakePostNumberDistinct < ActiveRecord::Migration[4.2]
def up def up
Topic.exec_sql('update posts p DB.exec('update posts p
set post_number = calc set post_number = calc
from from
( (

View File

@ -3,25 +3,25 @@ class AddLoungeCategory < ActiveRecord::Migration[4.2]
return if Rails.env.test? return if Rails.env.test?
I18n.overrides_disabled do I18n.overrides_disabled do
result = Category.exec_sql "SELECT 1 FROM site_settings where name = 'lounge_category_id'" result = DB.exec "SELECT 1 FROM site_settings where name = 'lounge_category_id'"
if result.count == 0 if result == 0
description = I18n.t('vip_category_description') description = I18n.t('vip_category_description')
default_name = I18n.t('vip_category_name') 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 default_name
else else
"CHANGE_ME" "CHANGE_ME"
end 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) (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) VALUES (:name, 'EEEEEE', '652D90', now(), now(), -1, '', :description, true, 3)
RETURNING id", name: name, description: description 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", WHERE id = :category_id",
slug: Slug.for(name, "#{category_id}-category"), category_id: category_id slug: Slug.for(name, "#{category_id}-category"), category_id: category_id

View File

@ -3,20 +3,20 @@ class AddMetaCategory < ActiveRecord::Migration[4.2]
return if Rails.env.test? return if Rails.env.test?
I18n.overrides_disabled do I18n.overrides_disabled do
result = Category.exec_sql "SELECT 1 FROM site_settings where name = 'meta_category_id'" result = DB.exec "SELECT 1 FROM site_settings where name = 'meta_category_id'"
if result.count == 0 if result == 0
description = I18n.t('meta_category_description') description = I18n.t('meta_category_description')
name = I18n.t('meta_category_name') name = I18n.t('meta_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) (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) VALUES (:name, '808281', 'FFFFFF', now(), now(), -1, :slug, :description, true, 1)
RETURNING id", name: name, slug: '', description: description 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 slug: Slug.for(name, "#{category_id}-category"), category_id: category_id
execute "INSERT INTO site_settings(name, data_type, value, created_at, updated_at) execute "INSERT INTO site_settings(name, data_type, value, created_at, updated_at)

View File

@ -3,24 +3,24 @@ class AddStaffCategory < ActiveRecord::Migration[4.2]
return if Rails.env.test? return if Rails.env.test?
I18n.overrides_disabled do I18n.overrides_disabled do
result = Category.exec_sql "SELECT 1 FROM site_settings where name = 'staff_category_id'" result = DB.exec "SELECT 1 FROM site_settings where name = 'staff_category_id'"
if result.count == 0 if result == 0
description = I18n.t('staff_category_description') description = I18n.t('staff_category_description')
name = I18n.t('staff_category_name') 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) (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) VALUES (:name, '283890', 'FFFFFF', now(), now(), -1, '', :description, true, 2)
RETURNING id", name: name, description: description 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 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())" VALUES ('staff_category_id', 3, #{category_id.to_i}, now(), now())"
end end
end end

View File

@ -1,10 +1,10 @@
class InitFixedCategoryPositionsValue < ActiveRecord::Migration[4.2] class InitFixedCategoryPositionsValue < ActiveRecord::Migration[4.2]
def up def up
# Look at existing categories to determine if positions have been specified # 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 # 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())" execute "INSERT INTO site_settings (name, data_type, value, created_at, updated_at) VALUES ('fixed_category_positions', 5, 't', now(), now())"
end end
end end

View File

@ -1,17 +1,17 @@
class GoogleOpenidDefaultHasChanged < ActiveRecord::Migration[4.2] class GoogleOpenidDefaultHasChanged < ActiveRecord::Migration[4.2]
def up def up
users_count_query = User.exec_sql("SELECT count(*) FROM users") users_count_query = DB.query_single("SELECT count(*) FROM users")
if users_count_query[0]['count'].to_i > 1 if users_count_query.first.to_i > 1
# This is an existing site. # This is an existing site.
result = User.exec_sql("SELECT count(*) FROM site_settings WHERE name = 'enable_google_logins'") result = DB.query_single("SELECT count(*) FROM site_settings WHERE name = 'enable_google_logins'")
if result[0]['count'].to_i == 0 if result.first.to_i == 0
# The old default was true, so add a row to keep it that way. # 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())" execute "INSERT INTO site_settings (name, data_type, value, created_at, updated_at) VALUES ('enable_google_logins', 5, 't', now(), now())"
end end
# Don't enable the new Google setting on an existing site. # 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'") result = DB.query_single("SELECT count(*) FROM site_settings WHERE name = 'enable_google_oauth2_logins'")
if result[0]['count'].to_i == 0 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())" execute "INSERT INTO site_settings (name, data_type, value, created_at, updated_at) VALUES ('enable_google_oauth2_logins', 5, 'f', now(), now())"
end end
end end

View File

@ -1,15 +1,15 @@
class DisableExternalAuthsByDefault < ActiveRecord::Migration[4.2] class DisableExternalAuthsByDefault < ActiveRecord::Migration[4.2]
def enable_setting_if_default(name) def enable_setting_if_default(name)
result = User.exec_sql("SELECT count(*) count FROM site_settings WHERE name = '#{name}'") result = DB.query_single("SELECT count(*) count FROM site_settings WHERE name = '#{name}'")
if result[0]['count'].to_i == 0 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())" execute "INSERT INTO site_settings (name, data_type, value, created_at, updated_at) VALUES ('#{name}', 5, 't', now(), now())"
end end
end end
def up def up
users_count_query = User.exec_sql("SELECT count(*) FROM users") users_count_query = DB.query_single("SELECT count(*) FROM users")
if users_count_query[0]['count'].to_i > 1 if users_count_query.first.to_i > 1
# existing site, so keep settings as they are # existing site, so keep settings as they are
enable_setting_if_default 'enable_yahoo_logins' enable_setting_if_default 'enable_yahoo_logins'
enable_setting_if_default 'enable_google_oauth2_logins' enable_setting_if_default 'enable_google_oauth2_logins'

View File

@ -1,16 +1,16 @@
class RemoveEmailInAddressSetting < ActiveRecord::Migration[4.2] class RemoveEmailInAddressSetting < ActiveRecord::Migration[4.2]
def up def up
uncat_id = ActiveRecord::Base.exec_sql("SELECT value FROM site_settings WHERE name = 'uncategorized_category_id'").first uncat_id = DB.query_single("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 cat_id_r = DB.query_single("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 email_r = DB.query_single("SELECT value FROM site_settings WHERE name = 'email_in_address'").first
if email_r if email_r
category_id = uncat_id["value"].to_i category_id = uncat_id["value"].to_i
category_id = cat_id_r["value"].to_i if cat_id_r category_id = cat_id_r["value"].to_i if cat_id_r
email = email_r["value"] 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 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 end
def down def down

View File

@ -1,14 +1,14 @@
class ResolveDuplicateGroupNames < ActiveRecord::Migration[4.2] class ResolveDuplicateGroupNames < ActiveRecord::Migration[4.2]
def up def up
results = Group.exec_sql 'SELECT id FROM groups results = DB.query_single 'SELECT id FROM groups
WHERE name ILIKE WHERE name ILIKE
(SELECT lower(name) (SELECT lower(name)
FROM groups FROM groups
GROUP BY lower(name) GROUP BY lower(name)
HAVING count(*) > 1);' 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| groups.group_by { |g| g.name.downcase }.each do |key, value|
value.each_with_index do |dup, index| value.each_with_index do |dup, index|
dup.update! name: "#{dup.name[0..18]}_#{index + 1}" if index > 0 dup.update! name: "#{dup.name[0..18]}_#{index + 1}" if index > 0

View File

@ -2,7 +2,7 @@ class FixCategoryLogoAndBackgroundUrls < ActiveRecord::Migration[4.2]
def up def up
return true if Discourse.asset_host.blank? return true if Discourse.asset_host.blank?
Category.exec_sql <<-SQL DB.exec <<-SQL
UPDATE categories UPDATE categories
SET logo_url = replace(logo_url, '#{Discourse.asset_host}', '') SET logo_url = replace(logo_url, '#{Discourse.asset_host}', '')
, background_url = replace(background_url, '#{Discourse.asset_host}', '') , background_url = replace(background_url, '#{Discourse.asset_host}', '')

View File

@ -1,7 +1,7 @@
class AddSeenAtToUserAuthToken < ActiveRecord::Migration[4.2] class AddSeenAtToUserAuthToken < ActiveRecord::Migration[4.2]
def up def up
add_column :user_auth_tokens, :seen_at, :datetime 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 end
def down def down

View File

@ -3,7 +3,7 @@ class SplitPublicInGroups < ActiveRecord::Migration[4.2]
add_column :groups, :public_exit, :boolean, default: false, null: false add_column :groups, :public_exit, :boolean, default: false, null: false
add_column :groups, :public_admission, :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 UPDATE groups
SET public_exit = true, public_admission = true SET public_exit = true, public_admission = true
WHERE public = true WHERE public = true

View File

@ -1,6 +1,6 @@
class DropRaiseReadOnlyFunction < ActiveRecord::Migration[5.1] class DropRaiseReadOnlyFunction < ActiveRecord::Migration[5.1]
def up def up
ActiveRecord::Base.exec_sql( DB.exec(
"DROP FUNCTION IF EXISTS raise_read_only() CASCADE;" "DROP FUNCTION IF EXISTS raise_read_only() CASCADE;"
) )
end end

View File

@ -76,7 +76,7 @@ module BackupRestore
end end
def self.move_tables_between_schemas(source, destination) 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 end
def self.move_tables_between_schemas_sql(source, destination) def self.move_tables_between_schemas_sql(source, destination)
@ -196,7 +196,7 @@ module BackupRestore
end end
def self.backup_tables_count 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
end end

View File

@ -379,14 +379,14 @@ module BackupRestore
@db_was_changed = true @db_was_changed = true
User.exec_sql(sql) DB.exec(sql)
end end
def migrate_database def migrate_database
log "Migrating the database..." log "Migrating the database..."
Discourse::Application.load_tasks Discourse::Application.load_tasks
ENV["VERSION"] = @current_version.to_s 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 Rake::Task["db:migrate"].invoke
end end

View File

@ -13,10 +13,10 @@ class CommentMigration < ActiveRecord::Migration[4.2]
comment = column[1] comment = column[1]
if column_name == :_table 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}" puts " COMMENT ON TABLE #{table_name}"
else 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}" puts " COMMENT ON COLUMN #{table_name}.#{column_name}"
end end
end end
@ -35,10 +35,10 @@ class CommentMigration < ActiveRecord::Migration[4.2]
comment = column[1] comment = column[1]
if column_name == :_table 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}" puts " COMMENT ON TABLE #{table_name}"
else 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}" puts " COMMENT ON COLUMN #{table_name}.#{column_name}"
end end
end end

View File

@ -73,7 +73,7 @@ class CookedPostProcessor
PostUpload.transaction do PostUpload.transaction do
PostUpload.where(post_id: @post.id).delete_all PostUpload.where(post_id: @post.id).delete_all
if upload_ids.size > 0 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 end
end end

View File

@ -48,7 +48,7 @@ module Migration
"Discourse: #{column_name} in #{table_name} is readonly" : "Discourse: #{column_name} in #{table_name} is readonly" :
"Discourse: #{table_name} is read only" "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$ CREATE OR REPLACE FUNCTION #{readonly_function_name(table_name, column_name)} RETURNS trigger AS $rcr$
BEGIN BEGIN
RAISE EXCEPTION '#{message}'; RAISE EXCEPTION '#{message}';

View File

@ -12,7 +12,7 @@ module Migration
def self.mark_readonly(table_name, column_name) def self.mark_readonly(table_name, column_name)
create_readonly_function(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)} CREATE TRIGGER #{readonly_trigger_name(table_name, column_name)}
BEFORE INSERT OR UPDATE OF #{column_name} BEFORE INSERT OR UPDATE OF #{column_name}
ON #{table_name} ON #{table_name}
@ -51,13 +51,13 @@ module Migration
def execute_drop! def execute_drop!
@columns.each do |column| @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 TRIGGER IF EXISTS #{BaseDropper.readonly_trigger_name(@table, column)} ON #{@table};
DROP FUNCTION IF EXISTS #{BaseDropper.readonly_function_name(@table, column)} CASCADE; DROP FUNCTION IF EXISTS #{BaseDropper.readonly_function_name(@table, column)} CASCADE;
SQL SQL
# safe cause it is protected on method entry, can not be passed in params # 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 end
end end

View File

@ -18,7 +18,7 @@ module Migration
def self.read_only_table(table_name) def self.read_only_table(table_name)
create_readonly_function(table_name) create_readonly_function(table_name)
ActiveRecord::Base.exec_sql <<~SQL DB.exec <<~SQL
CREATE TRIGGER #{readonly_trigger_name(table_name)} CREATE TRIGGER #{readonly_trigger_name(table_name)}
BEFORE INSERT OR UPDATE OR DELETE OR TRUNCATE BEFORE INSERT OR UPDATE OR DELETE OR TRUNCATE
ON #{table_name} ON #{table_name}
@ -37,7 +37,7 @@ module Migration
end end
def droppable? def droppable?
builder = SqlBuilder.new(<<~SQL) builder = DB.build(<<~SQL)
SELECT 1 SELECT 1
FROM INFORMATION_SCHEMA.TABLES FROM INFORMATION_SCHEMA.TABLES
/*where*/ /*where*/
@ -52,7 +52,7 @@ module Migration
.exec(old_name: @old_name, .exec(old_name: @old_name,
new_name: @new_name, new_name: @new_name,
delay: "#{@delay} seconds", delay: "#{@delay} seconds",
after_migration: @after_migration).to_a.length > 0 after_migration: @after_migration) > 0
end end
def table_exists(table_name_placeholder) def table_exists(table_name_placeholder)
@ -67,9 +67,9 @@ module Migration
end end
def execute_drop! 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; DROP FUNCTION IF EXISTS #{BaseDropper.readonly_function_name(@old_name)} CASCADE;
SQL SQL
end end

View File

@ -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

View File

@ -566,7 +566,7 @@ class PostRevisor
end end
def update_topic_word_counts def update_topic_word_counts
Topic.exec_sql("UPDATE topics DB.exec("UPDATE topics
SET word_count = ( SET word_count = (
SELECT SUM(COALESCE(posts.word_count, 0)) SELECT SUM(COALESCE(posts.word_count, 0))
FROM posts FROM posts

View File

@ -790,9 +790,11 @@ class Search
def self.ts_query(term: , ts_config: nil, joiner: "&", weight_filter: nil) def self.ts_query(term: , ts_config: nil, joiner: "&", weight_filter: nil)
data = Post.exec_sql("SELECT TO_TSVECTOR(:config, :term)", data = DB.query_single(
config: 'simple', "SELECT TO_TSVECTOR(:config, :term)",
term: term).values[0][0] config: 'simple',
term: term
).first
ts_config = ActiveRecord::Base.connection.quote(ts_config) if ts_config ts_config = ActiveRecord::Base.connection.quote(ts_config) if ts_config
all_terms = data.scan(/'([^']+)'\:\d+/).flatten all_terms = data.scan(/'([^']+)'\:\d+/).flatten

View File

@ -96,7 +96,7 @@ task 'db:stats' => 'environment' do
SQL SQL
puts puts
print_table(Post.exec_sql(sql).to_a) print_table(DB.query_hash(sql))
end end
desc 'Rebuild indexes' desc 'Rebuild indexes'
@ -108,16 +108,14 @@ task 'db:rebuild_indexes' => 'environment' do
Discourse.enable_readonly_mode Discourse.enable_readonly_mode
backup_schema = Jobs::Importer::BACKUP_SCHEMA 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| table_names = DB.query_single("select table_name from information_schema.tables where table_schema = 'public'")
row['table_name']
end
begin begin
# Move all tables to the backup schema: # Move all tables to the backup schema:
User.exec_sql("DROP SCHEMA IF EXISTS #{backup_schema} CASCADE") DB.exec("DROP SCHEMA IF EXISTS #{backup_schema} CASCADE")
User.exec_sql("CREATE SCHEMA #{backup_schema}") DB.exec("CREATE SCHEMA #{backup_schema}")
table_names.each do |table_name| 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 end
# Create a new empty db # Create a new empty db
@ -126,25 +124,25 @@ task 'db:rebuild_indexes' => 'environment' do
# Fetch index definitions from the new db # Fetch index definitions from the new db
index_definitions = {} index_definitions = {}
table_names.each do |table_name| 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 end
# Drop the new tables # Drop the new tables
table_names.each do |table_name| table_names.each do |table_name|
User.exec_sql("DROP TABLE public.#{table_name}") DB.exec("DROP TABLE public.#{table_name}")
end end
# Move the old tables back to the public schema # Move the old tables back to the public schema
table_names.each do |table_name| 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 end
# Drop their indexes # 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| index_names.each do |index_name|
begin begin
puts index_name puts index_name
User.exec_sql("DROP INDEX public.#{index_name}") DB.exec("DROP INDEX public.#{index_name}")
rescue ActiveRecord::StatementInvalid rescue ActiveRecord::StatementInvalid
# It's this: # It's this:
# PG::Error: ERROR: cannot drop index category_users_pkey because constraint category_users_pkey on table category_users requires it # 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| table_names.each do |table_name|
index_definitions[table_name].each do |index_def| index_definitions[table_name].each do |index_def|
begin begin
User.exec_sql(index_def) DB.exec(index_def)
rescue ActiveRecord::StatementInvalid rescue ActiveRecord::StatementInvalid
# Trying to recreate a primary key # Trying to recreate a primary key
end end

View File

@ -29,7 +29,7 @@ MS_SPEND_CREATING_POST ||= 5000
def insert_post_timings def insert_post_timings
log "Inserting post timings..." log "Inserting post timings..."
exec_sql <<-SQL DB.exec <<-SQL
INSERT INTO post_timings (topic_id, post_number, user_id, msecs) INSERT INTO post_timings (topic_id, post_number, user_id, msecs)
SELECT topic_id, post_number, user_id, #{MS_SPEND_CREATING_POST} SELECT topic_id, post_number, user_id, #{MS_SPEND_CREATING_POST}
FROM posts FROM posts
@ -41,7 +41,7 @@ end
def insert_post_replies def insert_post_replies
log "Inserting post replies..." log "Inserting post replies..."
exec_sql <<-SQL DB.exec <<-SQL
INSERT INTO post_replies (post_id, reply_id, created_at, updated_at) INSERT INTO post_replies (post_id, reply_id, created_at, updated_at)
SELECT p2.id, p.id, p.created_at, p.created_at SELECT p2.id, p.id, p.created_at, p.created_at
FROM posts p FROM posts p
@ -53,7 +53,7 @@ end
def insert_topic_users def insert_topic_users
log "Inserting 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) 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} 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 FROM posts
@ -66,7 +66,7 @@ end
def insert_topic_views def insert_topic_views
log "Inserting topic views..." log "Inserting topic views..."
exec_sql <<-SQL DB.exec <<-SQL
WITH X AS ( WITH X AS (
SELECT topic_id, user_id, DATE(p.created_at) posted_at SELECT topic_id, user_id, DATE(p.created_at) posted_at
FROM posts p FROM posts p
@ -86,7 +86,7 @@ end
def insert_user_actions def insert_user_actions
log "Inserting user actions for NEW_TOPIC = 4..." 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) 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 SELECT 4, p.user_id, topic_id, p.id, p.user_id, p.created_at, p.created_at
FROM posts p FROM posts p
@ -100,7 +100,7 @@ def insert_user_actions
log "Inserting user actions for REPLY = 5..." 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) 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 SELECT 5, p.user_id, topic_id, p.id, p.user_id, p.created_at, p.created_at
FROM posts p FROM posts p
@ -114,7 +114,7 @@ def insert_user_actions
log "Inserting user actions for RESPONSE = 6..." 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) 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 SELECT 6, p.user_id, p.topic_id, p.id, p2.user_id, p.created_at, p.created_at
FROM posts p FROM posts p
@ -137,7 +137,7 @@ end
def insert_user_options def insert_user_options
log "Inserting user options..." log "Inserting user options..."
exec_sql <<-SQL DB.exec <<-SQL
INSERT INTO user_options ( INSERT INTO user_options (
user_id, user_id,
email_always, email_always,
@ -189,7 +189,7 @@ end
def insert_user_stats def insert_user_stats
log "Inserting user stats..." log "Inserting user stats..."
exec_sql <<-SQL DB.exec <<-SQL
INSERT INTO user_stats (user_id, new_since) INSERT INTO user_stats (user_id, new_since)
SELECT id, created_at SELECT id, created_at
FROM users FROM users
@ -200,7 +200,7 @@ end
def insert_user_visits def insert_user_visits
log "Inserting user visits..." log "Inserting user visits..."
exec_sql <<-SQL DB.exec <<-SQL
INSERT INTO user_visits (user_id, visited_at, posts_read) INSERT INTO user_visits (user_id, visited_at, posts_read)
SELECT user_id, DATE(created_at), COUNT(*) SELECT user_id, DATE(created_at), COUNT(*)
FROM posts FROM posts
@ -213,7 +213,7 @@ end
def insert_draft_sequences def insert_draft_sequences
log "Inserting draft sequences..." log "Inserting draft sequences..."
exec_sql <<-SQL DB.exec <<-SQL
INSERT INTO draft_sequences (user_id, draft_key, sequence) INSERT INTO draft_sequences (user_id, draft_key, sequence)
SELECT user_id, CONCAT('#{Draft::EXISTING_TOPIC}', id), 1 SELECT user_id, CONCAT('#{Draft::EXISTING_TOPIC}', id), 1
FROM topics FROM topics
@ -226,7 +226,7 @@ end
def update_user_stats def update_user_stats
log "Updating user stats..." log "Updating user stats..."
exec_sql <<-SQL DB.exec <<-SQL
WITH X AS ( WITH X AS (
SELECT p.user_id SELECT p.user_id
, COUNT(p.id) posts , COUNT(p.id) posts
@ -283,7 +283,7 @@ end
def update_posts def update_posts
log "Updating posts..." log "Updating posts..."
exec_sql <<-SQL DB.exec <<-SQL
WITH Y AS ( WITH Y AS (
SELECT post_id, COUNT(*) replies FROM post_replies GROUP BY post_id SELECT post_id, COUNT(*) replies FROM post_replies GROUP BY post_id
) )
@ -310,7 +310,7 @@ end
def update_topics def update_topics
log "Updating topics..." log "Updating topics..."
exec_sql <<-SQL DB.exec <<-SQL
WITH X AS ( WITH X AS (
SELECT topic_id SELECT topic_id
, COUNT(*) posts , COUNT(*) posts
@ -350,7 +350,7 @@ end
def update_categories def update_categories
log "Updating categories..." log "Updating categories..."
exec_sql <<-SQL DB.exec <<-SQL
WITH X AS ( WITH X AS (
SELECT category_id SELECT category_id
, MAX(p.id) post_id , MAX(p.id) post_id
@ -382,7 +382,7 @@ end
def update_users def update_users
log "Updating users..." log "Updating users..."
exec_sql <<-SQL DB.exec <<-SQL
WITH X AS ( WITH X AS (
SELECT user_id SELECT user_id
, MIN(created_at) min_created_at , MIN(created_at) min_created_at
@ -406,7 +406,7 @@ end
def update_groups def update_groups
log "Updating groups..." log "Updating groups..."
exec_sql <<-SQL DB.exec <<-SQL
WITH X AS ( WITH X AS (
SELECT group_id, COUNT(*) count SELECT group_id, COUNT(*) count
FROM group_users FROM group_users
@ -428,12 +428,6 @@ def log(message)
puts "[#{DateTime.now.strftime("%Y-%m-%d %H:%M:%S")}] #{message}" puts "[#{DateTime.now.strftime("%Y-%m-%d %H:%M:%S")}] #{message}"
end end
def exec_sql(sql)
ActiveRecord::Base.transaction do
ActiveRecord::Base.exec_sql(sql)
end
end
task "import:create_phpbb_permalinks" => :environment do task "import:create_phpbb_permalinks" => :environment do
log 'Creating Permalinks...' log 'Creating Permalinks...'
@ -477,7 +471,6 @@ task "import:remap_old_phpbb_permalinks" => :environment do
# skip # skip
end end
end end
i
log "Done! #{i} posts remapped." log "Done! #{i} posts remapped."
end end

View File

@ -304,7 +304,7 @@ task 'posts:reorder_posts', [:topic_id] => [:environment] do |_, args|
builder.where("topic_id = :topic_id") if args[:topic_id] builder.where("topic_id = :topic_id") if args[:topic_id]
builder.exec(topic_id: args[:topic_id]) builder.exec(topic_id: args[:topic_id])
Notification.exec_sql(<<~SQL) DB.exec(<<~SQL)
UPDATE notifications AS x UPDATE notifications AS x
SET post_number = p.sort_order SET post_number = p.sort_order
FROM posts AS p FROM posts AS p
@ -313,7 +313,7 @@ task 'posts:reorder_posts', [:topic_id] => [:environment] do |_, args|
p.post_number < 0 p.post_number < 0
SQL SQL
PostTiming.exec_sql(<<~SQL) DB.exec(<<~SQL)
UPDATE post_timings AS x UPDATE post_timings AS x
SET post_number = x.post_number * -1 SET post_number = x.post_number * -1
FROM posts AS p FROM posts AS p
@ -329,7 +329,7 @@ task 'posts:reorder_posts', [:topic_id] => [:environment] do |_, args|
p.post_number < 0; p.post_number < 0;
SQL SQL
Post.exec_sql(<<~SQL) DB.exec(<<~SQL)
UPDATE posts AS x UPDATE posts AS x
SET reply_to_post_number = p.sort_order SET reply_to_post_number = p.sort_order
FROM posts AS p FROM posts AS p
@ -338,7 +338,7 @@ task 'posts:reorder_posts', [:topic_id] => [:environment] do |_, args|
p.post_number < 0; p.post_number < 0;
SQL SQL
TopicUser.exec_sql(<<~SQL) DB.exec(<<~SQL)
UPDATE topic_users AS x UPDATE topic_users AS x
SET last_read_post_number = p.sort_order SET last_read_post_number = p.sort_order
FROM posts AS p FROM posts AS p
@ -362,7 +362,7 @@ task 'posts:reorder_posts', [:topic_id] => [:environment] do |_, args|
SQL SQL
# finally update the post_number # finally update the post_number
Post.exec_sql(<<~SQL) DB.exec(<<~SQL)
UPDATE posts UPDATE posts
SET post_number = sort_order SET post_number = sort_order
WHERE post_number < 0 WHERE post_number < 0

View File

@ -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' printf "%-25s | %-25s | %-25s | %-25s\n", 'username', 'total size of uploads', 'number of uploads', 'number of optimized images'
puts "-" * 110 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 printf "%-25s | %-25s | %-25s | %-25s\n", username, helper.number_to_human_size(total_size_of_uploads), num_of_uploads, num_of_optimized_images
end end

View File

@ -285,14 +285,14 @@ class TopicView
sql = <<~SQL sql = <<~SQL
SELECT user_id, count(*) AS count_all SELECT user_id, count(*) AS count_all
FROM posts FROM posts
WHERE id IN (:post_ids) WHERE id in (:post_ids)
AND user_id IS NOT NULL AND user_id IS NOT NULL
GROUP BY user_id GROUP BY user_id
ORDER BY count_all DESC ORDER BY count_all DESC
LIMIT #{MAX_PARTICIPANTS} LIMIT #{MAX_PARTICIPANTS}
SQL SQL
Hash[Post.exec_sql(sql, post_ids: post_ids).values] Hash[*DB.query_single(sql, post_ids: post_ids)]
end end
end end
@ -306,7 +306,7 @@ class TopicView
WHERE id IN (:post_ids) WHERE id IN (:post_ids)
AND user_id IS NOT NULL AND user_id IS NOT NULL
SQL 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 else
participants.size participants.size
end end

View File

@ -72,7 +72,7 @@ class TopicsBulkAction
WHERE t.id = tu.topic_id AND tu.user_id = :user_id AND t.id IN (:topic_ids) 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 @changed_ids.concat @topic_ids
end end

View File

@ -626,7 +626,7 @@ class ImportScripts::Base
def update_topic_status def update_topic_status
puts "", "Updating topic status" puts "", "Updating topic status"
Topic.exec_sql(<<~SQL) DB.exec(<<~SQL)
UPDATE topics AS t UPDATE topics AS t
SET closed = TRUE SET closed = TRUE
WHERE EXISTS( WHERE EXISTS(
@ -636,7 +636,7 @@ class ImportScripts::Base
) )
SQL SQL
Topic.exec_sql(<<~SQL) DB.exec(<<~SQL)
UPDATE topics AS t UPDATE topics AS t
SET archived = TRUE SET archived = TRUE
WHERE EXISTS( WHERE EXISTS(
@ -646,7 +646,7 @@ class ImportScripts::Base
) )
SQL SQL
TopicCustomField.exec_sql(<<~SQL) DB.exec(<<~SQL)
DELETE FROM topic_custom_fields DELETE FROM topic_custom_fields
WHERE name IN ('import_closed', 'import_archived') WHERE name IN ('import_closed', 'import_archived')
SQL SQL
@ -654,7 +654,7 @@ class ImportScripts::Base
def update_bumped_at def update_bumped_at
puts "", "Updating bumped_at on topics" 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 end
def update_last_posted_at def update_last_posted_at
@ -674,7 +674,7 @@ class ImportScripts::Base
AND users.last_posted_at <> lpa.last_posted_at AND users.last_posted_at <> lpa.last_posted_at
SQL SQL
User.exec_sql(sql) DB.exec(sql)
end end
def update_user_stats def update_user_stats
@ -707,7 +707,7 @@ class ImportScripts::Base
AND user_stats.first_post_created_at <> sub.first_post_created_at AND user_stats.first_post_created_at <> sub.first_post_created_at
SQL SQL
User.exec_sql(sql) DB.exec(sql)
puts "", "Updating user post_count..." puts "", "Updating user post_count..."
@ -725,7 +725,7 @@ class ImportScripts::Base
AND user_stats.post_count <> sub.post_count AND user_stats.post_count <> sub.post_count
SQL SQL
User.exec_sql(sql) DB.exec(sql)
puts "", "Updating user topic_count..." puts "", "Updating user topic_count..."
@ -743,15 +743,15 @@ class ImportScripts::Base
AND user_stats.topic_count <> sub.topic_count AND user_stats.topic_count <> sub.topic_count
SQL SQL
User.exec_sql(sql) DB.exec(sql)
end end
# scripts that are able to import last_seen_at from the source data should override this method # scripts that are able to import last_seen_at from the source data should override this method
def update_last_seen_at def update_last_seen_at
puts "", "Updating last seen at on users" puts "", "Updating last seen at on users"
User.exec_sql("UPDATE users SET last_seen_at = created_at WHERE last_seen_at IS NULL") DB.exec("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 = last_posted_at WHERE last_posted_at IS NOT NULL")
end end
def update_feature_topic_users def update_feature_topic_users

View File

@ -267,7 +267,7 @@ class ImportScripts::IPBoard3 < ImportScripts::Base
WHERE id IN (SELECT topic_id FROM closed_topic_ids) WHERE id IN (SELECT topic_id FROM closed_topic_ids)
SQL SQL
Topic.exec_sql(sql, @closed_topic_ids) DB.exec(sql, @closed_topic_ids)
end end
def import_personal_topics def import_personal_topics

View File

@ -325,7 +325,7 @@ class ImportScripts::JiveApi < ImportScripts::Base
def mark_topics_as_solved def mark_topics_as_solved
puts "", "Marking 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) 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 SELECT 'accepted_answer_post_id', pcf.post_id, p.topic_id, p.created_at, p.created_at
FROM post_custom_fields pcf FROM post_custom_fields pcf

View File

@ -535,7 +535,7 @@ class ImportScripts::Lithium < ImportScripts::Base
end end
puts "loading data into temp table" 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 PostAction.transaction do
results.each do |result| results.each do |result|
@ -544,17 +544,17 @@ class ImportScripts::Lithium < ImportScripts::Base
next unless result["user_id"] && result["post_id"] next unless result["user_id"] && result["post_id"]
PostAction.exec_sql("INSERT INTO like_data VALUES (:user_id,:post_id,:created_at)", DB.exec("INSERT INTO like_data VALUES (:user_id,:post_id,:created_at)",
user_id: result["user_id"], user_id: result["user_id"],
post_id: result["post_id"], post_id: result["post_id"],
created_at: result["created_at"] created_at: result["created_at"]
) )
end end
end end
puts "creating missing post actions" 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) 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 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 SQL
puts "creating missing user actions" 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) 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 SELECT pa.user_id, 1, p.topic_id, p.id, pa.user_id, pa.created_at, pa.created_at
FROM post_actions pa FROM post_actions pa
@ -574,7 +574,7 @@ class ImportScripts::Lithium < ImportScripts::Base
SQL SQL
# reverse action # 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) 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 SELECT p.user_id, 2, p.topic_id, p.id, pa.user_id, pa.created_at, pa.created_at
FROM post_actions pa FROM post_actions pa
@ -586,7 +586,7 @@ class ImportScripts::Lithium < ImportScripts::Base
SQL SQL
puts "updating like counts on posts" puts "updating like counts on posts"
Post.exec_sql <<-SQL DB.exec <<~SQL
UPDATE posts SET like_count = coalesce(cnt,0) UPDATE posts SET like_count = coalesce(cnt,0)
FROM ( FROM (
SELECT post_id, count(*) cnt SELECT post_id, count(*) cnt
@ -600,7 +600,7 @@ class ImportScripts::Lithium < ImportScripts::Base
puts "updating like counts on topics" puts "updating like counts on topics"
Post.exec_sql <<-SQL DB.exec <<-SQL
UPDATE topics SET like_count = coalesce(cnt,0) UPDATE topics SET like_count = coalesce(cnt,0)
FROM ( FROM (
SELECT topic_id, sum(like_count) cnt SELECT topic_id, sum(like_count) cnt
@ -627,7 +627,7 @@ class ImportScripts::Lithium < ImportScripts::Base
end end
puts "loading data into temp table" 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 PostAction.transaction do
results.each do |result| results.each do |result|
@ -635,7 +635,7 @@ class ImportScripts::Lithium < ImportScripts::Base
next unless result["post_id"] 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"] post_id: result["post_id"]
) )
@ -643,7 +643,7 @@ class ImportScripts::Lithium < ImportScripts::Base
end end
puts "deleting dupe answers" puts "deleting dupe answers"
PostAction.exec_sql <<-SQL DB.exec <<~SQL
DELETE FROM accepted_data WHERE post_id NOT IN ( DELETE FROM accepted_data WHERE post_id NOT IN (
SELECT post_id FROM SELECT post_id FROM
( (
@ -656,7 +656,7 @@ class ImportScripts::Lithium < ImportScripts::Base
SQL SQL
puts "importing accepted answers" puts "importing accepted answers"
PostAction.exec_sql <<-SQL DB.exec <<~SQL
INSERT into post_custom_fields (name, value, post_id, created_at, updated_at) 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 SELECT 'is_accepted_answer', 'true', a.post_id, current_timestamp, current_timestamp
FROM accepted_data a FROM accepted_data a
@ -665,7 +665,7 @@ class ImportScripts::Lithium < ImportScripts::Base
SQL SQL
puts "marking accepted topics" puts "marking accepted topics"
PostAction.exec_sql <<-SQL DB.exec <<~SQL
INSERT into topic_custom_fields (name, value, topic_id, created_at, updated_at) 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 SELECT 'accepted_answer_post_id', a.post_id::varchar, p.topic_id, current_timestamp, current_timestamp
FROM accepted_data a FROM accepted_data a
@ -797,10 +797,10 @@ class ImportScripts::Lithium < ImportScripts::Base
results.map { |r| r["post_id"] }.each_slice(500) do |ids| results.map { |r| r["post_id"] }.each_slice(500) do |ids|
mapped = ids.map { |id| existing_map[id] }.compact mapped = ids.map { |id| existing_map[id] }.compact
Topic.exec_sql(" DB.exec(<<~SQL, ids: mapped) if mapped.present?
UPDATE topics SET closed = true UPDATE topics SET closed = true
WHERE id IN (SELECT topic_id FROM posts where id in (:ids)) WHERE id IN (SELECT topic_id FROM posts where id in (:ids))
", ids: mapped) if mapped.present? SQL
end end
end end
@ -819,8 +819,8 @@ class ImportScripts::Lithium < ImportScripts::Base
WHERE pm.id IS NULL AND f.name = 'import_unique_id' WHERE pm.id IS NULL AND f.name = 'import_unique_id'
SQL SQL
r = Permalink.exec_sql sql r = DB.exec sql
puts "#{r.cmd_tuples} permalinks to topics added!" puts "#{r} permalinks to topics added!"
sql = <<-SQL sql = <<-SQL
INSERT INTO permalinks (url, post_id, created_at, updated_at) 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' WHERE pm.id IS NULL AND f.name = 'import_unique_id'
SQL SQL
r = Permalink.exec_sql sql r = DB.exec sql
puts "#{r.cmd_tuples} permalinks to posts added!" puts "#{r} permalinks to posts added!"
end end

View File

@ -236,7 +236,7 @@ FROM #{TABLE_PREFIX}discuss_users
def not_mark_topics_as_solved def not_mark_topics_as_solved
puts "", "Marking 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) 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 SELECT 'accepted_answer_post_id', pcf.post_id, p.topic_id, p.created_at, p.created_at
FROM post_custom_fields pcf FROM post_custom_fields pcf
@ -469,7 +469,7 @@ FROM #{TABLE_PREFIX}discuss_users
WHERE id IN (SELECT topic_id FROM closed_topic_ids) WHERE id IN (SELECT topic_id FROM closed_topic_ids)
SQL SQL
Topic.exec_sql(sql, closed_topic_ids) DB.exec(sql, closed_topic_ids)
end end
def not_post_process_posts def not_post_process_posts

View File

@ -237,7 +237,7 @@ class ImportScripts::StackOverflow < ImportScripts::Base
def mark_topics_as_solved def mark_topics_as_solved
puts "", "Marking 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) 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 SELECT 'accepted_answer_post_id', pcf.post_id, p.topic_id, p.created_at, p.created_at
FROM post_custom_fields pcf FROM post_custom_fields pcf

View File

@ -182,10 +182,8 @@ EOM
next if user_ids_in_group.size == 0 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(",") values = user_ids_in_group.map { |user_id| "(#{group.id}, #{user_id}, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)" }.join(",")
User.exec_sql <<-SQL DB.exec <<~SQL
BEGIN; INSERT INTO group_users (group_id, user_id, created_at, updated_at) VALUES #{values}
INSERT INTO group_users (group_id, user_id, created_at, updated_at) VALUES #{values};
COMMIT;
SQL SQL
Group.reset_counters(group.id, :group_users) Group.reset_counters(group.id, :group_users)
@ -634,7 +632,7 @@ EOM
WHERE id IN (SELECT topic_id FROM closed_topic_ids) WHERE id IN (SELECT topic_id FROM closed_topic_ids)
SQL SQL
Topic.exec_sql(sql, closed_topic_ids) DB.exec(sql, closed_topic_ids)
end end
def post_process_posts def post_process_posts

View File

@ -418,7 +418,7 @@ class ImportScripts::VBulletin < ImportScripts::Base
WHERE id IN (SELECT topic_id FROM closed_topic_ids) WHERE id IN (SELECT topic_id FROM closed_topic_ids)
SQL SQL
Topic.exec_sql(sql, @closed_topic_ids) DB.exec(sql, @closed_topic_ids)
end end
def post_process_posts def post_process_posts

Some files were not shown because too many files have changed in this diff Show More