mirror of
https://github.com/discourse/discourse.git
synced 2025-05-30 15:28:37 +08:00
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:
@ -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
|
||||
|
Reference in New Issue
Block a user