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 'oj'
gem 'pg', '~> 0.21.0'
gem 'pg'
gem 'mini_sql'
gem 'pry-rails', require: false
gem 'r2', '~> 0.2.5', require: false
gem 'rake'

View File

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

View File

@ -4,19 +4,6 @@ class Admin::DiagnosticsController < Admin::AdminController
layout false
skip_before_action :check_xhr
def dump_statement_cache
statements = Post.exec_sql("select * from pg_prepared_statements").to_a
text = ""
statements.each do |row|
text << "name: #{row["name"]} sql: #{row["statement"]}\n"
end
text << "\n\nCOUNT #{statements.count}"
render plain: text
end
def memory_stats
text = nil

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,9 +1,9 @@
module Jobs
class InitCategoryTagStats < Jobs::Onceoff
def execute_onceoff(args)
CategoryTagStat.exec_sql "DELETE FROM category_tag_stats"
DB.exec "DELETE FROM category_tag_stats"
CategoryTagStat.exec_sql <<~SQL
DB.exec <<~SQL
INSERT INTO category_tag_stats (category_id, tag_id, topic_count)
SELECT topics.category_id, tags.id, COUNT(topics.id)
FROM tags

View File

@ -1,9 +1,9 @@
module Jobs
class MigrateCensoredWords < Jobs::Onceoff
def execute_onceoff(args)
row = WatchedWord.exec_sql("SELECT value FROM site_settings WHERE name = 'censored_words'")
row = DB.query_single("SELECT value FROM site_settings WHERE name = 'censored_words'")
if row.count > 0
row.first["value"].split('|').each do |word|
row.first.split('|').each do |word|
WatchedWord.create(word: word, action: WatchedWord.actions[:censor])
end
end

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)
SQL
UserVisit.exec_sql(sql)
DB.exec(sql)
end
end
end

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,3 +1,5 @@
# frozen_string_literal: true
class DirectoryItem < ActiveRecord::Base
belongs_to :user
has_one :user_stat, foreign_key: :user_id, primary_key: :user_id
@ -42,7 +44,7 @@ class DirectoryItem < ActiveRecord::Base
ActiveRecord::Base.transaction do
# Delete records that belonged to users who have been deleted
exec_sql "DELETE FROM directory_items
DB.exec "DELETE FROM directory_items
USING directory_items di
LEFT JOIN users u ON (u.id = user_id AND u.active AND u.silenced_till IS NULL AND u.id > 0)
WHERE di.id = directory_items.id AND
@ -50,7 +52,7 @@ class DirectoryItem < ActiveRecord::Base
di.period_type = :period_type", period_type: period_types[period_type]
# Create new records for users who don't have one yet
exec_sql "INSERT INTO directory_items(period_type, user_id, likes_received, likes_given, topics_entered, days_visited, posts_read, topic_count, post_count)
DB.exec "INSERT INTO directory_items(period_type, user_id, likes_received, likes_given, topics_entered, days_visited, posts_read, topic_count, post_count)
SELECT
:period_type,
u.id,
@ -72,7 +74,7 @@ class DirectoryItem < ActiveRecord::Base
# TODO
# WARNING: post_count is a wrong name, it should be reply_count (excluding topic post)
#
exec_sql "WITH x AS (SELECT
DB.exec "WITH x AS (SELECT
u.id user_id,
SUM(CASE WHEN p.id IS NOT NULL AND t.id IS NOT NULL AND ua.action_type = :was_liked_type THEN 1 ELSE 0 END) likes_received,
SUM(CASE WHEN p.id IS NOT NULL AND t.id IS NOT NULL AND ua.action_type = :like_type THEN 1 ELSE 0 END) likes_given,
@ -121,23 +123,22 @@ class DirectoryItem < ActiveRecord::Base
regular_post_type: Post.types[:regular]
if period_type == :all
exec_sql <<SQL
UPDATE user_stats s
SET likes_given = d.likes_given,
likes_received = d.likes_received,
topic_count = d.topic_count,
post_count = d.post_count
DB.exec <<~SQL
UPDATE user_stats s
SET likes_given = d.likes_given,
likes_received = d.likes_received,
topic_count = d.topic_count,
post_count = d.post_count
FROM directory_items d
WHERE s.user_id = d.user_id AND
d.period_type = 1 AND
( s.likes_given <> d.likes_given OR
s.likes_received <> d.likes_received OR
s.topic_count <> d.topic_count OR
s.post_count <> d.post_count
)
SQL
FROM directory_items d
WHERE s.user_id = d.user_id AND
d.period_type = 1 AND
( s.likes_given <> d.likes_given OR
s.likes_received <> d.likes_received OR
s.topic_count <> d.topic_count OR
s.post_count <> d.post_count
)
SQL
end
end
end

View File

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

View File

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

View File

@ -12,7 +12,7 @@ class EmojiSetSiteSetting < EnumSiteSetting
after = "/images/emoji/#{site_setting.value}/"
Scheduler::Defer.later("Fix Emoji Links") do
Post.exec_sql("UPDATE posts SET cooked = REPLACE(cooked, :before, :after) WHERE cooked LIKE :like",
DB.exec("UPDATE posts SET cooked = REPLACE(cooked, :before, :after) WHERE cooked LIKE :like",
before: before,
after: after,
like: "%#{before}%"

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -340,7 +340,7 @@ SQL
def self.copy(original_post, target_post)
cols_to_copy = (column_names - %w{id post_id}).join(', ')
exec_sql <<~SQL
DB.exec <<~SQL
INSERT INTO post_actions(post_id, #{cols_to_copy})
SELECT #{target_post.id}, #{cols_to_copy}
FROM post_actions
@ -425,26 +425,29 @@ SQL
# Returns the flag counts for a post, taking into account that some users
# can weigh flags differently.
def self.flag_counts_for(post_id)
flag_counts = exec_sql("SELECT SUM(CASE
WHEN pa.disagreed_at IS NULL AND pa.staff_took_action THEN :flags_required_to_hide_post
WHEN pa.disagreed_at IS NULL AND NOT pa.staff_took_action THEN 1
ELSE 0
END) AS new_flags,
SUM(CASE
WHEN pa.disagreed_at IS NOT NULL AND pa.staff_took_action THEN :flags_required_to_hide_post
WHEN pa.disagreed_at IS NOT NULL AND NOT pa.staff_took_action THEN 1
ELSE 0
END) AS old_flags
FROM post_actions AS pa
INNER JOIN users AS u ON u.id = pa.user_id
WHERE pa.post_id = :post_id
AND pa.post_action_type_id IN (:post_action_types)
AND pa.deleted_at IS NULL",
post_id: post_id,
post_action_types: PostActionType.auto_action_flag_types.values,
flags_required_to_hide_post: SiteSetting.flags_required_to_hide_post).first
params = {
post_id: post_id,
post_action_types: PostActionType.auto_action_flag_types.values,
flags_required_to_hide_post: SiteSetting.flags_required_to_hide_post
}
[flag_counts['old_flags'].to_i, flag_counts['new_flags'].to_i]
DB.query_single(<<~SQL, params)
SELECT COALESCE(SUM(CASE
WHEN pa.disagreed_at IS NOT NULL AND pa.staff_took_action THEN :flags_required_to_hide_post
WHEN pa.disagreed_at IS NOT NULL AND NOT pa.staff_took_action THEN 1
ELSE 0
END),0) AS old_flags,
COALESCE(SUM(CASE
WHEN pa.disagreed_at IS NULL AND pa.staff_took_action THEN :flags_required_to_hide_post
WHEN pa.disagreed_at IS NULL AND NOT pa.staff_took_action THEN 1
ELSE 0
END), 0) AS new_flags
FROM post_actions AS pa
INNER JOIN users AS u ON u.id = pa.user_id
WHERE pa.post_id = :post_id
AND pa.post_action_type_id in (:post_action_types)
AND pa.deleted_at IS NULL
SQL
end
def post_action_type_key

View File

@ -10,7 +10,7 @@ class PostRevision < ActiveRecord::Base
def self.ensure_consistency!
# 1 - fix the numbers
PostRevision.exec_sql <<-SQL
DB.exec <<-SQL
UPDATE post_revisions
SET number = pr.rank
FROM (SELECT id, 1 + ROW_NUMBER() OVER (PARTITION BY post_id ORDER BY number, created_at, updated_at) AS rank FROM post_revisions) AS pr
@ -19,7 +19,7 @@ class PostRevision < ActiveRecord::Base
SQL
# 2 - fix the versions on the posts
PostRevision.exec_sql <<-SQL
DB.exec <<-SQL
UPDATE posts
SET version = 1 + (SELECT COUNT(*) FROM post_revisions WHERE post_id = posts.id),
public_version = 1 + (SELECT COUNT(*) FROM post_revisions pr WHERE post_id = posts.id AND pr.hidden = 'f')

View File

@ -12,7 +12,7 @@ class PostTiming < ActiveRecord::Base
def self.pretend_read(topic_id, actual_read_post_number, pretend_read_post_number)
# This is done in SQL cause the logic is quite tricky and we want to do this in one db hit
#
exec_sql("INSERT INTO post_timings(topic_id, user_id, post_number, msecs)
DB.exec("INSERT INTO post_timings(topic_id, user_id, post_number, msecs)
SELECT :topic_id, user_id, :pretend_read_post_number, 1
FROM post_timings pt
WHERE topic_id = :topic_id AND
@ -34,7 +34,7 @@ class PostTiming < ActiveRecord::Base
def self.record_new_timing(args)
begin
exec_sql("INSERT INTO post_timings (topic_id, user_id, post_number, msecs)
DB.exec("INSERT INTO post_timings (topic_id, user_id, post_number, msecs)
SELECT :topic_id, :user_id, :post_number, :msecs
WHERE NOT EXISTS(SELECT 1 FROM post_timings
WHERE topic_id = :topic_id
@ -53,12 +53,13 @@ class PostTiming < ActiveRecord::Base
# Increases a timer if a row exists, otherwise create it
def self.record_timing(args)
rows = exec_sql_row_count("UPDATE post_timings
SET msecs = msecs + :msecs
WHERE topic_id = :topic_id
AND user_id = :user_id
AND post_number = :post_number",
args)
rows = DB.exec(<<~SQL, args)
UPDATE post_timings
SET msecs = msecs + :msecs
WHERE topic_id = :topic_id
AND user_id = :user_id
AND post_number = :post_number
SQL
record_new_timing(args) if rows == 0
end
@ -115,9 +116,7 @@ class PostTiming < ActiveRecord::Base
RETURNING x.idx
SQL
result = exec_sql(sql)
result.type_map = SqlBuilder.pg_type_map
existing = Set.new(result.column_values(0))
existing = Set.new(DB.query_single(sql))
sql = <<~SQL
SELECT 1 FROM topics
@ -126,7 +125,7 @@ SQL
id = :topic_id
SQL
is_regular = Post.exec_sql(sql, topic_id: topic_id).cmd_tuples == 1
is_regular = DB.exec(sql, topic_id: topic_id) == 1
new_posts_read = timings.size - existing.size if is_regular
timings.each_with_index do |(post_number, time), index|

View File

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

View File

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

View File

@ -24,7 +24,7 @@ class StylesheetCache < ActiveRecord::Base
.pluck(:id)
.last
exec_sql("DELETE FROM stylesheet_cache where id < :id", id: remove_lower)
DB.exec("DELETE FROM stylesheet_cache where id < :id", id: remove_lower)
end
success

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -208,7 +208,7 @@ class User < ActiveRecord::Base
def self.username_available?(username, email = nil)
lower = username.downcase
return false if reserved_username?(lower)
return true if User.exec_sql(User::USERNAME_EXISTS_SQL, username: lower).count == 0
return true if DB.exec(User::USERNAME_EXISTS_SQL, username: lower) == 0
# staged users can use the same username since they will take over the account
email.present? && User.joins(:user_emails).exists?(staged: true, username_lower: lower, user_emails: { primary: true, email: email })
@ -387,7 +387,8 @@ class User < ActiveRecord::Base
AND NOT read
SQL
User.exec_sql(sql, user_id: id, type: notification_type).getvalue(0, 0).to_i
# to avoid coalesce we do to_i
DB.query_single(sql, user_id: id, type: notification_type)[0].to_i
end
def unread_private_messages
@ -408,11 +409,11 @@ class User < ActiveRecord::Base
AND NOT read
SQL
User.exec_sql(sql,
DB.query_single(sql,
user_id: id,
seen_notification_id: seen_notification_id,
pm: Notification.types[:private_message]
).getvalue(0, 0).to_i
)[0].to_i
end
end
@ -446,7 +447,7 @@ class User < ActiveRecord::Base
notification = notifications.visible.order('notifications.id desc').first
json = NotificationSerializer.new(notification).as_json if notification
sql = "
sql = (<<~SQL).freeze
SELECT * FROM (
SELECT n.id, n.read FROM notifications n
LEFT JOIN topics t ON n.topic_id = t.id
@ -469,13 +470,13 @@ class User < ActiveRecord::Base
ORDER BY n.id DESC
LIMIT 20
) AS y
"
SQL
recent = User.exec_sql(sql,
recent = DB.query(sql,
user_id: id,
type: Notification.types[:private_message]
).values.map! do |id, read|
[id.to_i, read]
).map! do |r|
[r.id, r.read]
end
payload = {
@ -1155,12 +1156,12 @@ class User < ActiveRecord::Base
end
USERNAME_EXISTS_SQL = <<~SQL
(SELECT users.id AS user_id FROM users
(SELECT users.id AS id, true as is_user FROM users
WHERE users.username_lower = :username)
UNION ALL
(SELECT groups.id AS group_id FROM groups
(SELECT groups.id, false as is_user FROM groups
WHERE lower(groups.name) = :username)
SQL
@ -1168,11 +1169,14 @@ class User < ActiveRecord::Base
username_format_validator || begin
lower = username.downcase
existing = User.exec_sql(
existing = DB.query(
USERNAME_EXISTS_SQL, username: lower
).to_a.first
)
if will_save_change_to_username? && existing.present? && existing["user_id"] != self.id
user_id = existing.select { |u| u.is_user }.first&.id
same_user = user_id && user_id == self.id
if will_save_change_to_username? && existing.present? && !same_user
errors.add(:username, I18n.t(:'user.username.unique'))
end
end
@ -1200,7 +1204,7 @@ class User < ActiveRecord::Base
end
if values.present?
exec_sql("INSERT INTO category_users (user_id, category_id, notification_level) VALUES #{values.join(",")}")
DB.exec("INSERT INTO category_users (user_id, category_id, notification_level) VALUES #{values.join(",")}")
end
end

View File

@ -97,7 +97,8 @@ SQL
AND t.id IN (SELECT topic_id FROM topic_allowed_users WHERE user_id = :user_id)
SQL
all, mine, unread = exec_sql(sql, user_id: user_id).values[0].map(&:to_i)
# map is there due to count returning nil
all, mine, unread = DB.query_single(sql, user_id: user_id).map(&:to_i)
sql = <<-SQL
SELECT g.name, COUNT(*) "count"
@ -112,8 +113,8 @@ SQL
result = { all: all, mine: mine, unread: unread }
exec_sql(sql, user_id: user_id).each do |row|
(result[:groups] ||= []) << { name: row["name"], count: row["count"].to_i }
DB.query(sql, user_id: user_id).each do |row|
(result[:groups] ||= []) << { name: row.name, count: row.count.to_i }
end
result

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -194,16 +194,18 @@ class PostAlerter
}
def group_stats(topic)
sql = <<~SQL
SELECT COUNT(*) FROM topics t
JOIN topic_allowed_groups g ON g.group_id = :group_id AND g.topic_id = t.id
LEFT JOIN group_archived_messages a ON a.topic_id = t.id AND a.group_id = g.group_id
WHERE a.id IS NULL AND t.deleted_at is NULL AND t.archetype = 'private_message'
SQL
topic.allowed_groups.map do |g|
{
group_id: g.id,
group_name: g.name.downcase,
inbox_count: Topic.exec_sql(
"SELECT COUNT(*) FROM topics t
JOIN topic_allowed_groups g ON g.group_id = :group_id AND g.topic_id = t.id
LEFT JOIN group_archived_messages a ON a.topic_id = t.id AND a.group_id = g.group_id
WHERE a.id IS NULL AND t.deleted_at is NULL AND t.archetype = 'private_message'",
group_id: g.id).values[0][0].to_i
inbox_count: DB.query_single(sql, group_id: g.id).first.to_i
}
end
end

View File

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

View File

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

View File

@ -143,17 +143,18 @@ class UserUpdater
MutedUser.where('user_id = ? AND muted_user_id not in (?)', user.id, desired_ids).destroy_all
# SQL is easier here than figuring out how to do the same in AR
MutedUser.exec_sql("INSERT into muted_users(user_id, muted_user_id, created_at, updated_at)
SELECT :user_id, id, :now, :now
FROM users
WHERE
id in (:desired_ids) AND
id NOT IN (
SELECT muted_user_id
FROM muted_users
WHERE user_id = :user_id
)",
now: Time.now, user_id: user.id, desired_ids: desired_ids)
DB.exec(<<~SQL, now: Time.now, user_id: user.id, desired_ids: desired_ids)
INSERT into muted_users(user_id, muted_user_id, created_at, updated_at)
SELECT :user_id, id, :now, :now
FROM users
WHERE
id in (:desired_ids) AND
id NOT IN (
SELECT muted_user_id
FROM muted_users
WHERE user_id = :user_id
)
SQL
end
end

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 "dump_heap" => "diagnostics#dump_heap", constraints: AdminConstraint.new
get "dump_statement_cache" => "diagnostics#dump_statement_cache", constraints: AdminConstraint.new
end # admin namespace
get "email_preferences" => "email#preferences_redirect", :as => "email_preferences_redirect"

View File

@ -48,7 +48,7 @@ Migration::ColumnDropper.drop(
},
on_drop: ->() {
STDERR.puts "Removing superflous user stats columns!"
ActiveRecord::Base.exec_sql "DROP FUNCTION IF EXISTS first_unread_topic_for(int)"
DB.exec "DROP FUNCTION IF EXISTS first_unread_topic_for(int)"
}
)

View File

@ -10,18 +10,18 @@ uncat_id = -1 unless Numeric === uncat_id
if uncat_id == -1 || !Category.exists?(uncat_id)
puts "Seeding uncategorized category!"
result = Category.exec_sql "SELECT 1 FROM categories WHERE lower(name) = 'uncategorized'"
count = DB.exec "SELECT 1 FROM categories WHERE lower(name) = 'uncategorized'"
name = 'Uncategorized'
name << SecureRandom.hex if result.count > 0
name << SecureRandom.hex if count > 0
result = Category.exec_sql "INSERT INTO categories
result = DB.query_single "INSERT INTO categories
(name,color,slug,description,text_color, user_id, created_at, updated_at, position, name_lower)
VALUES ('#{name}', 'AB9364', 'uncategorized', '', 'FFFFFF', -1, now(), now(), 1, '#{name.downcase}' )
RETURNING id
"
category_id = result[0]["id"].to_i
category_id = result.first.to_i
Category.exec_sql "DELETE FROM site_settings where name = 'uncategorized_category_id'"
Category.exec_sql "INSERT INTO site_settings(name, data_type, value, created_at, updated_at)
DB.exec "DELETE FROM site_settings where name = 'uncategorized_category_id'"
DB.exec "INSERT INTO site_settings(name, data_type, value, created_at, updated_at)
VALUES ('uncategorized_category_id', 3, #{category_id}, now(), now())"
end

View File

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

View File

@ -36,7 +36,7 @@ unless Rails.env.test?
end
# Reset topic count because we don't count the description topic
Category.exec_sql "UPDATE categories SET topic_count = 0 WHERE id = #{lounge.id}"
DB.exec "UPDATE categories SET topic_count = 0 WHERE id = #{lounge.id}"
end
end
end

View File

@ -25,7 +25,7 @@ unless Rails.env.test?
end
# Reset topic count because we don't count the description topic
Category.exec_sql "UPDATE categories SET topic_count = 0 WHERE id = #{meta.id}"
DB.exec "UPDATE categories SET topic_count = 0 WHERE id = #{meta.id}"
end
end
end

View File

@ -33,7 +33,7 @@ unless Rails.env.test?
end
# Reset topic count because we don't count the description topic
Category.exec_sql "UPDATE categories SET topic_count = 0 WHERE id = #{staff.id}"
DB.exec "UPDATE categories SET topic_count = 0 WHERE id = #{staff.id}"
end
end
end

View File

@ -1,7 +1,7 @@
class AddStarredAtToForumThreadUser < ActiveRecord::Migration[4.2]
def up
add_column :forum_thread_users, :starred_at, :datetime
User.exec_sql 'update forum_thread_users f set starred_at = COALESCE(created_at, ?)
DB.exec 'update forum_thread_users f set starred_at = COALESCE(created_at, ?)
from
(
select f1.forum_thread_id, f1.user_id, t.created_at from forum_thread_users f1

View File

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

View File

@ -3,25 +3,25 @@ class AddLoungeCategory < ActiveRecord::Migration[4.2]
return if Rails.env.test?
I18n.overrides_disabled do
result = Category.exec_sql "SELECT 1 FROM site_settings where name = 'lounge_category_id'"
if result.count == 0
result = DB.exec "SELECT 1 FROM site_settings where name = 'lounge_category_id'"
if result == 0
description = I18n.t('vip_category_description')
default_name = I18n.t('vip_category_name')
name = if Category.exec_sql("SELECT 1 FROM categories where name = '#{default_name}'").count == 0
name = if DB.exec("SELECT 1 FROM categories where name = '#{default_name}'") == 0
default_name
else
"CHANGE_ME"
end
result = Category.exec_sql "INSERT INTO categories
result = DB.query_single "INSERT INTO categories
(name, color, text_color, created_at, updated_at, user_id, slug, description, read_restricted, position)
VALUES (:name, 'EEEEEE', '652D90', now(), now(), -1, '', :description, true, 3)
RETURNING id", name: name, description: description
category_id = result[0]["id"].to_i
category_id = result.first.to_i
Category.exec_sql "UPDATE categories SET slug = :slug
DB.exec "UPDATE categories SET slug = :slug
WHERE id = :category_id",
slug: Slug.for(name, "#{category_id}-category"), category_id: category_id

View File

@ -3,20 +3,20 @@ class AddMetaCategory < ActiveRecord::Migration[4.2]
return if Rails.env.test?
I18n.overrides_disabled do
result = Category.exec_sql "SELECT 1 FROM site_settings where name = 'meta_category_id'"
if result.count == 0
result = DB.exec "SELECT 1 FROM site_settings where name = 'meta_category_id'"
if result == 0
description = I18n.t('meta_category_description')
name = I18n.t('meta_category_name')
if Category.exec_sql("SELECT 1 FROM categories where name ilike :name", name: name).count == 0
result = Category.exec_sql "INSERT INTO categories
if DB.exec("SELECT 1 FROM categories where name ilike :name", name: name) == 0
result = DB.query_single "INSERT INTO categories
(name, color, text_color, created_at, updated_at, user_id, slug, description, read_restricted, position)
VALUES (:name, '808281', 'FFFFFF', now(), now(), -1, :slug, :description, true, 1)
RETURNING id", name: name, slug: '', description: description
category_id = result[0]["id"].to_i
category_id = result.first.to_i
Category.exec_sql "UPDATE categories SET slug=:slug WHERE id=:category_id",
DB.exec "UPDATE categories SET slug=:slug WHERE id=:category_id",
slug: Slug.for(name, "#{category_id}-category"), category_id: category_id
execute "INSERT INTO site_settings(name, data_type, value, created_at, updated_at)

View File

@ -3,24 +3,24 @@ class AddStaffCategory < ActiveRecord::Migration[4.2]
return if Rails.env.test?
I18n.overrides_disabled do
result = Category.exec_sql "SELECT 1 FROM site_settings where name = 'staff_category_id'"
if result.count == 0
result = DB.exec "SELECT 1 FROM site_settings where name = 'staff_category_id'"
if result == 0
description = I18n.t('staff_category_description')
name = I18n.t('staff_category_name')
if Category.exec_sql("SELECT 1 FROM categories where name ilike :name", name: name).count == 0
if DB.exec("SELECT 1 FROM categories where name ilike :name", name: name) == 0
result = Category.exec_sql "INSERT INTO categories
result = DB.query_single "INSERT INTO categories
(name, color, text_color, created_at, updated_at, user_id, slug, description, read_restricted, position)
VALUES (:name, '283890', 'FFFFFF', now(), now(), -1, '', :description, true, 2)
RETURNING id", name: name, description: description
category_id = result[0]["id"].to_i
category_id = result.first.to_i
Category.exec_sql "UPDATE categories SET slug=:slug WHERE id=:category_id",
DB.exec "UPDATE categories SET slug=:slug WHERE id=:category_id",
slug: Slug.for(name, "#{category_id}-category"), category_id: category_id
execute "INSERT INTO site_settings(name, data_type, value, created_at, updated_at)
DB.exec "INSERT INTO site_settings(name, data_type, value, created_at, updated_at)
VALUES ('staff_category_id', 3, #{category_id.to_i}, now(), now())"
end
end

View File

@ -1,10 +1,10 @@
class InitFixedCategoryPositionsValue < ActiveRecord::Migration[4.2]
def up
# Look at existing categories to determine if positions have been specified
result = Category.exec_sql("SELECT count(*) FROM categories WHERE position IS NOT NULL")
result = DB.query_single("SELECT count(*) FROM categories WHERE position IS NOT NULL")
# Greater than 4 because uncategorized, meta, staff, lounge all have positions by default
if result[0]['count'].to_i > 4
if result.first.to_i > 4
execute "INSERT INTO site_settings (name, data_type, value, created_at, updated_at) VALUES ('fixed_category_positions', 5, 't', now(), now())"
end
end

View File

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

View File

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

View File

@ -1,16 +1,16 @@
class RemoveEmailInAddressSetting < ActiveRecord::Migration[4.2]
def up
uncat_id = ActiveRecord::Base.exec_sql("SELECT value FROM site_settings WHERE name = 'uncategorized_category_id'").first
cat_id_r = ActiveRecord::Base.exec_sql("SELECT value FROM site_settings WHERE name = 'email_in_category'").first
email_r = ActiveRecord::Base.exec_sql("SELECT value FROM site_settings WHERE name = 'email_in_address'").first
uncat_id = DB.query_single("SELECT value FROM site_settings WHERE name = 'uncategorized_category_id'").first
cat_id_r = DB.query_single("SELECT value FROM site_settings WHERE name = 'email_in_category'").first
email_r = DB.query_single("SELECT value FROM site_settings WHERE name = 'email_in_address'").first
if email_r
category_id = uncat_id["value"].to_i
category_id = cat_id_r["value"].to_i if cat_id_r
email = email_r["value"]
ActiveRecord::Base.exec_sql("UPDATE categories SET email_in = ? WHERE id = ?", email, category_id)
DB.exec("UPDATE categories SET email_in = ? WHERE id = ?", email, category_id)
end
ActiveRecord::Base.exec_sql("DELETE FROM site_settings WHERE name = 'email_in_category' OR name = 'email_in_address'")
DB.exec("DELETE FROM site_settings WHERE name = 'email_in_category' OR name = 'email_in_address'")
end
def down

View File

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

View File

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

View File

@ -1,7 +1,7 @@
class AddSeenAtToUserAuthToken < ActiveRecord::Migration[4.2]
def up
add_column :user_auth_tokens, :seen_at, :datetime
ActiveRecord::Base.exec_sql "UPDATE user_auth_tokens SET seen_at = :now WHERE auth_token_seen", now: Time.zone.now
DB.exec "UPDATE user_auth_tokens SET seen_at = :now WHERE auth_token_seen", now: Time.zone.now
end
def down

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_admission, :boolean, default: false, null: false
ActiveRecord::Base.exec_sql <<~SQL
DB.exec <<~SQL
UPDATE groups
SET public_exit = true, public_admission = true
WHERE public = true

View File

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

View File

@ -76,7 +76,7 @@ module BackupRestore
end
def self.move_tables_between_schemas(source, destination)
User.exec_sql(move_tables_between_schemas_sql(source, destination))
DB.exec(move_tables_between_schemas_sql(source, destination))
end
def self.move_tables_between_schemas_sql(source, destination)
@ -196,7 +196,7 @@ module BackupRestore
end
def self.backup_tables_count
User.exec_sql("SELECT COUNT(*) AS count FROM information_schema.tables WHERE table_schema = 'backup'")[0]['count'].to_i
DB.query_single("SELECT COUNT(*) AS count FROM information_schema.tables WHERE table_schema = 'backup'").first.to_i
end
end

View File

@ -379,14 +379,14 @@ module BackupRestore
@db_was_changed = true
User.exec_sql(sql)
DB.exec(sql)
end
def migrate_database
log "Migrating the database..."
Discourse::Application.load_tasks
ENV["VERSION"] = @current_version.to_s
User.exec_sql("SET search_path = public, pg_catalog;")
DB.exec("SET search_path = public, pg_catalog;")
Rake::Task["db:migrate"].invoke
end

View File

@ -13,10 +13,10 @@ class CommentMigration < ActiveRecord::Migration[4.2]
comment = column[1]
if column_name == :_table
ActiveRecord::Base.exec_sql "COMMENT ON TABLE #{table_name} IS ?", comment
DB.exec "COMMENT ON TABLE #{table_name} IS ?", comment
puts " COMMENT ON TABLE #{table_name}"
else
ActiveRecord::Base.exec_sql "COMMENT ON COLUMN #{table_name}.#{column_name} IS ?", comment
DB.exec "COMMENT ON COLUMN #{table_name}.#{column_name} IS ?", comment
puts " COMMENT ON COLUMN #{table_name}.#{column_name}"
end
end
@ -35,10 +35,10 @@ class CommentMigration < ActiveRecord::Migration[4.2]
comment = column[1]
if column_name == :_table
ActiveRecord::Base.exec_sql "COMMENT ON TABLE #{table_name} IS ?", comment
DB.exec "COMMENT ON TABLE #{table_name} IS ?", comment
puts " COMMENT ON TABLE #{table_name}"
else
ActiveRecord::Base.exec_sql "COMMENT ON COLUMN #{table_name}.#{column_name} IS ?", comment
DB.exec "COMMENT ON COLUMN #{table_name}.#{column_name} IS ?", comment
puts " COMMENT ON COLUMN #{table_name}.#{column_name}"
end
end

View File

@ -73,7 +73,7 @@ class CookedPostProcessor
PostUpload.transaction do
PostUpload.where(post_id: @post.id).delete_all
if upload_ids.size > 0
PostUpload.exec_sql("INSERT INTO post_uploads (post_id, upload_id) VALUES #{values}")
DB.exec("INSERT INTO post_uploads (post_id, upload_id) VALUES #{values}")
end
end
end

View File

@ -48,7 +48,7 @@ module Migration
"Discourse: #{column_name} in #{table_name} is readonly" :
"Discourse: #{table_name} is read only"
ActiveRecord::Base.exec_sql <<~SQL
DB.exec <<~SQL
CREATE OR REPLACE FUNCTION #{readonly_function_name(table_name, column_name)} RETURNS trigger AS $rcr$
BEGIN
RAISE EXCEPTION '#{message}';

View File

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

View File

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

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
def update_topic_word_counts
Topic.exec_sql("UPDATE topics
DB.exec("UPDATE topics
SET word_count = (
SELECT SUM(COALESCE(posts.word_count, 0))
FROM posts

View File

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

View File

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

View File

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

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

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'
puts "-" * 110
User.exec_sql(sql).values.each do |username, num_of_uploads, total_size_of_uploads, num_of_optimized_images|
DB.query_single(sql).each do |username, num_of_uploads, total_size_of_uploads, num_of_optimized_images|
printf "%-25s | %-25s | %-25s | %-25s\n", username, helper.number_to_human_size(total_size_of_uploads), num_of_uploads, num_of_optimized_images
end

View File

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

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)
"
Topic.exec_sql(sql, user_id: @user.id, topic_ids: @topic_ids)
DB.exec(sql, user_id: @user.id, topic_ids: @topic_ids)
@changed_ids.concat @topic_ids
end

View File

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

View File

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

View File

@ -325,7 +325,7 @@ class ImportScripts::JiveApi < ImportScripts::Base
def mark_topics_as_solved
puts "", "Marking topics as solved..."
PostAction.exec_sql <<-SQL
DB.exec <<~SQL
INSERT INTO topic_custom_fields (name, value, topic_id, created_at, updated_at)
SELECT 'accepted_answer_post_id', pcf.post_id, p.topic_id, p.created_at, p.created_at
FROM post_custom_fields pcf

View File

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

View File

@ -236,7 +236,7 @@ FROM #{TABLE_PREFIX}discuss_users
def not_mark_topics_as_solved
puts "", "Marking topics as solved..."
PostAction.exec_sql <<-SQL
DB.exec <<~SQL
INSERT INTO topic_custom_fields (name, value, topic_id, created_at, updated_at)
SELECT 'accepted_answer_post_id', pcf.post_id, p.topic_id, p.created_at, p.created_at
FROM post_custom_fields pcf
@ -469,7 +469,7 @@ FROM #{TABLE_PREFIX}discuss_users
WHERE id IN (SELECT topic_id FROM closed_topic_ids)
SQL
Topic.exec_sql(sql, closed_topic_ids)
DB.exec(sql, closed_topic_ids)
end
def not_post_process_posts

View File

@ -237,7 +237,7 @@ class ImportScripts::StackOverflow < ImportScripts::Base
def mark_topics_as_solved
puts "", "Marking topics as solved..."
Topic.exec_sql <<~SQL
DB.exec <<~SQL
INSERT INTO topic_custom_fields (name, value, topic_id, created_at, updated_at)
SELECT 'accepted_answer_post_id', pcf.post_id, p.topic_id, p.created_at, p.created_at
FROM post_custom_fields pcf

View File

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

View File

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

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