This commit is contained in:
Dull Bananas 2024-05-17 20:21:13 +00:00
parent 614e0e59d1
commit 5d8af0d476
20 changed files with 3127 additions and 115 deletions

View file

@ -185,44 +185,6 @@ steps:
- cargo test --workspace --no-fail-fast
when: *slow_check_paths
check_diesel_migration:
# TODO: use willsquire/diesel-cli image when shared libraries become optional in lemmy_server
image: *rust_image
environment:
LEMMY_DATABASE_URL: postgres://lemmy:password@database:5432/lemmy
RUST_BACKTRACE: "1"
CARGO_HOME: .cargo_home
DATABASE_URL: postgres://lemmy:password@database:5432/lemmy
PGUSER: lemmy
PGPASSWORD: password
PGHOST: database
PGDATABASE: lemmy
commands:
- cargo install diesel_cli
- export PATH="$CARGO_HOME/bin:$PATH"
# Run all migrations
- diesel migration run
# Dump schema to before.sqldump (PostgreSQL apt repo is used to prevent pg_dump version mismatch error)
- apt update && apt install -y lsb-release
- sh -c 'echo "deb https://apt.postgresql.org/pub/repos/apt $(lsb_release -cs)-pgdg main" > /etc/apt/sources.list.d/pgdg.list'
- wget --quiet -O - https://www.postgresql.org/media/keys/ACCC4CF8.asc | apt-key add -
- apt update && apt install -y postgresql-client-16
- psql -c "DROP SCHEMA IF EXISTS r CASCADE;"
- pg_dump --no-owner --no-privileges --no-table-access-method --schema-only --no-sync -f before.sqldump
# Make sure that the newest migration is revertable without the `r` schema
- diesel migration redo
# Run schema setup twice, which fails on the 2nd time if `DROP SCHEMA IF EXISTS r CASCADE` drops the wrong things
- alias lemmy_schema_setup="target/lemmy_server --disable-scheduled-tasks --disable-http-server --disable-activity-sending"
- lemmy_schema_setup
- lemmy_schema_setup
# Make sure that the newest migration is revertable with the `r` schema
- diesel migration redo
# Check for changes in the schema, which would be caused by an incorrect migration
- psql -c "DROP SCHEMA IF EXISTS r CASCADE;"
- pg_dump --no-owner --no-privileges --no-table-access-method --schema-only --no-sync -f after.sqldump
- diff before.sqldump after.sqldump
when: *slow_check_paths
run_federation_tests:
image: node:20-bookworm-slim
environment:

View file

@ -11,11 +11,6 @@ extern crate diesel_derive_newtype;
#[macro_use]
extern crate diesel_derive_enum;
// this is used in tests
#[cfg(feature = "full")]
#[macro_use]
extern crate diesel_migrations;
#[cfg(feature = "full")]
#[macro_use]
extern crate async_trait;

View file

@ -1,9 +1,8 @@
use crate::schema::previously_run_sql;
use anyhow::Context;
use anyhow::{anyhow, Context};
use diesel::{
backend::Backend,
connection::SimpleConnection,
migration::{Migration, MigrationSource},
migration::{Migration, MigrationSource, MigrationVersion},
pg::Pg,
select,
update,
@ -14,12 +13,25 @@ use diesel::{
QueryDsl,
RunQueryDsl,
};
use diesel_migrations::{EmbeddedMigrations, MigrationHarness};
use diesel_migrations::MigrationHarness;
use lemmy_utils::error::{LemmyError, LemmyResult};
use std::time::Instant;
use tracing::info;
const MIGRATIONS: EmbeddedMigrations = embed_migrations!();
// In production, include migrations in the binary
#[cfg(not(debug_assertions))]
fn get_migration_source() -> diesel_migrations::EmbeddedMigrations {
// Using `const` here is required by the borrow checker
const MIGRATIONS: diesel_migrations::EmbeddedMigrations = diesel_migrations::embed_migrations!();
MIGRATIONS
}
// Avoid recompiling when migrations are changed
#[cfg(debug_assertions)]
fn get_migration_source() -> diesel_migrations::FileBasedMigrations {
diesel_migrations::FileBasedMigrations::find_migrations_directory()
.expect("failed to find migrations dir")
}
/// This SQL code sets up the `r` schema, which contains things that can be safely dropped and replaced
/// instead of being changed using migrations. It may not create or modify things outside of the `r` schema
@ -30,35 +42,103 @@ const REPLACEABLE_SCHEMA: &[&str] = &[
include_str!("../replaceable_schema/triggers.sql"),
];
fn get_pending_migrations(conn: &mut PgConnection) -> LemmyResult<Vec<Box<dyn Migration<Pg>>>> {
Ok(
conn
.pending_migrations(MIGRATIONS)
.map_err(|e| anyhow::anyhow!("Couldn't determine pending migrations: {e}"))?,
)
struct MigrationHarnessWrapper<'a> {
conn: &'a mut PgConnection,
}
impl<'a> MigrationHarness<Pg> for MigrationHarnessWrapper<'a> {
fn run_migration(
&mut self,
migration: &dyn Migration<Pg>,
) -> diesel::migration::Result<MigrationVersion<'static>> {
let start_time = Instant::now();
let result = self.conn.run_migration(migration);
let duration = start_time.elapsed().as_millis();
let name = migration.name();
info!("{duration}ms run {name}");
result
}
fn revert_migration(
&mut self,
migration: &dyn Migration<Pg>,
) -> diesel::migration::Result<MigrationVersion<'static>> {
let start_time = Instant::now();
let result = self.conn.revert_migration(migration);
let duration = start_time.elapsed().as_millis();
let name = migration.name();
info!("{duration}ms revert {name}");
result
}
fn applied_migrations(&mut self) -> diesel::migration::Result<Vec<MigrationVersion<'static>>> {
self.conn.applied_migrations()
}
}
// TODO: remove when diesel either adds MigrationSource impl for references or changes functions to take reference
#[derive(Clone, Copy)]
struct MigrationSourceRef<T>(
// If this was `&T`, then the derive macros would add `Clone` and `Copy` bounds for `T`
T,
);
impl<'a, T: MigrationSource<Pg>> MigrationSource<Pg> for MigrationSourceRef<&'a T> {
fn migrations(&self) -> diesel::migration::Result<Vec<Box<dyn Migration<Pg>>>> {
self.0.migrations()
}
}
#[derive(Default)]
pub struct Options {
/// Only for testing
enable_forbid_diesel_cli_trigger: bool,
revert: bool,
revert_amount: Option<u64>,
redo_after_revert: bool,
}
pub fn run(db_url: &str, options: &Options) -> LemmyResult<()> {
impl Options {
#[cfg(test)]
fn enable_forbid_diesel_cli_trigger(mut self) -> Self {
self.enable_forbid_diesel_cli_trigger = true;
self
}
pub fn revert(mut self, amount: Option<u64>) -> Self {
self.revert = true;
self.revert_amount = amount;
self
}
pub fn redo(mut self, amount: Option<u64>) -> Self {
self.redo_after_revert = true;
self.revert(amount)
}
}
pub fn run(db_url: &str, options: Options) -> LemmyResult<()> {
// Migrations don't support async connection, and this function doesn't need to be async
let mut conn = PgConnection::establish(db_url).with_context(|| "Error connecting to database")?;
let test_enabled = std::env::var("LEMMY_TEST_MIGRATIONS")
.map(|s| !s.is_empty())
.unwrap_or(false);
let new_sql = REPLACEABLE_SCHEMA.join("\n");
let pending_migrations = get_pending_migrations(&mut conn)?;
let migration_source = get_migration_source();
let migration_source_ref = MigrationSourceRef(&migration_source);
// If possible, skip locking the migrations table and recreating the "r" schema, so
// lemmy_server processes in a horizontally scaled setup can start without causing locks
if pending_migrations.is_empty() {
if !(options.revert
|| conn
.has_pending_migration(migration_source_ref)
.map_err(|e| anyhow!("Couldn't check pending migrations: {e}"))?)
{
// The condition above implies that the migration that creates the previously_run_sql table was already run
let sql_unchanged: bool = select(
previously_run_sql::table
@ -75,46 +155,61 @@ pub fn run(db_url: &str, options: &Options) -> LemmyResult<()> {
}
conn.transaction::<_, LemmyError, _>(|conn| {
// Use the table created by `MigrationHarness::pending_migrations` as a lock target to prevent multiple
// lemmy_server processes from running this transaction concurrently. This lock does not block
// `MigrationHarness::pending_migrations` (`SELECT`) or `MigrationHarness::run_migration` (`INSERT`).
let mut wrapper = MigrationHarnessWrapper { conn };
// * Prevent other lemmy_server processes from running this transaction simultaneously by repurposing
// the table created by `MigrationHarness::pending_migrations` as a lock target (this doesn't block
// normal use of the table)
// * Drop `r` schema, so migrations don't need to be made to work both with and without things in
// it existing
// * Disable the trigger that prevents the Diesel CLI from running migrations
info!("Waiting for lock...");
conn.batch_execute("LOCK __diesel_schema_migrations IN SHARE UPDATE EXCLUSIVE MODE;")?;
info!("Running Database migrations (This may take a long time)...");
// Check pending migrations again after locking
let pending_migrations = get_pending_migrations(conn)?;
// Drop `r` schema and disable the trigger that prevents the Diesel CLI from running migrations
let enable_migrations = if options.enable_forbid_diesel_cli_trigger {
""
} else {
"SET LOCAL lemmy.enable_migrations TO 'on';"
};
conn.batch_execute(&format!(
"DROP SCHEMA IF EXISTS r CASCADE;{enable_migrations}"
))?;
for migration in &pending_migrations {
let name = migration.name();
let start_time = Instant::now();
conn
.run_migration(migration)
.map_err(|e| anyhow::anyhow!("Couldn't run migration {name}: {e}"))?;
let duration = start_time.elapsed().as_millis();
info!("{duration}ms run {name}");
wrapper.conn.batch_execute(&format!("LOCK __diesel_schema_migrations IN SHARE UPDATE EXCLUSIVE MODE;DROP SCHEMA IF EXISTS r CASCADE;{enable_migrations}"))?;
info!("Running Database migrations (This may take a long time)...");
(|| {
if options.revert {
if let Some(amount) = options.revert_amount {
for _ in 0..amount {
wrapper.revert_last_migration(migration_source_ref)?;
}
if options.redo_after_revert {
for _ in 0..amount {
wrapper.run_next_migration(migration_source_ref)?;
}
}
} else {
wrapper.revert_all_migrations(migration_source_ref)?;
if options.redo_after_revert {
wrapper.run_pending_migrations(migration_source_ref)?;
}
}
} else {
wrapper.run_pending_migrations(migration_source_ref)?;
}
diesel::migration::Result::Ok(())
})().map_err(|e| anyhow!("Couldn't run DB Migrations: {e}"))?;
// Run replaceable_schema
conn
// Run replaceable_schema if newest migration was applied
if !(options.revert && !options.redo_after_revert) {
wrapper.conn
.batch_execute(&new_sql)
.context("Couldn't run SQL files in crates/db_schema/replaceable_schema")?;
let num_rows_updated = update(previously_run_sql::table)
.set(previously_run_sql::content.eq(new_sql))
.execute(conn)?;
.execute(wrapper.conn)?;
debug_assert_eq!(num_rows_updated, 1);
}
Ok(())
})?;
@ -126,16 +221,31 @@ pub fn run(db_url: &str, options: &Options) -> LemmyResult<()> {
#[cfg(test)]
mod tests {
use super::*;
use lemmy_utils::{error::LemmyResult, settings::SETTINGS};
use serial_test::serial;
#[test]
#[serial]
fn test_schema_setup() -> LemmyResult<()> {
let mut options = super::Options::default();
let db_url = SETTINGS.get_database_url();
let url = SETTINGS.get_database_url();
let mut conn = PgConnection::establish(&url)?;
// Test the forbid_diesel_cli trigger
options.enable_forbid_diesel_cli_trigger = true;
super::run(&db_url, &options).expect_err("forbid_diesel_cli trigger should throw error");
// Start with consistent state by dropping everything
conn.batch_execute("DROP OWNED BY CURRENT_USER;")?;
// Run and revert all migrations, ensuring there's no mistakes in any down.sql file
run(&url, Options::default())?;
run(&url, Options::default().revert(None))?;
// TODO also don't drop r, and maybe just directly call the migrationharness method here
assert!(matches!(
run(&url, Options::default().enable_forbid_diesel_cli_trigger()),
Err(e) if e.to_string().contains("lemmy_server")
));
// Previous run shouldn't stop this one from working
run(&url, Options::default())?;
Ok(())
}

View file

@ -427,7 +427,7 @@ pub async fn build_db_pool() -> LemmyResult<ActualDbPool> {
}))
.build()?;
crate::schema_setup::run(&db_url, &Default::default())?;
crate::schema_setup::run(&db_url, Default::default())?;
Ok(pool)
}

View file

@ -1,5 +1,11 @@
DROP TABLE activity;
DROP VIEW community_view, community_mview;
DROP MATERIALIZED VIEW community_aggregates_mview;
DROP VIEW community_aggregates_view;
ALTER TABLE user_
DROP COLUMN actor_id,
DROP COLUMN private_key,
@ -15,3 +21,126 @@ ALTER TABLE community
DROP COLUMN local,
DROP COLUMN last_refreshed_at;
-- Views are the same as before, except `*` does not reference the dropped columns
CREATE VIEW community_aggregates_view AS
SELECT
c.*,
(
SELECT
name
FROM
user_ u
WHERE
c.creator_id = u.id) AS creator_name,
(
SELECT
avatar
FROM
user_ u
WHERE
c.creator_id = u.id) AS creator_avatar,
(
SELECT
name
FROM
category ct
WHERE
c.category_id = ct.id) AS category_name,
(
SELECT
count(*)
FROM
community_follower cf
WHERE
cf.community_id = c.id) AS number_of_subscribers,
(
SELECT
count(*)
FROM
post p
WHERE
p.community_id = c.id) AS number_of_posts,
(
SELECT
count(*)
FROM
comment co,
post p
WHERE
c.id = p.community_id
AND p.id = co.post_id) AS number_of_comments,
hot_rank ((
SELECT
count(*)
FROM community_follower cf
WHERE
cf.community_id = c.id), c.published) AS hot_rank
FROM
community c;
CREATE MATERIALIZED VIEW community_aggregates_mview AS
SELECT
*
FROM
community_aggregates_view;
CREATE UNIQUE INDEX idx_community_aggregates_mview_id ON community_aggregates_mview (id);
CREATE VIEW community_view AS
with all_community AS (
SELECT
ca.*
FROM
community_aggregates_view ca
)
SELECT
ac.*,
u.id AS user_id,
(
SELECT
cf.id::boolean
FROM
community_follower cf
WHERE
u.id = cf.user_id
AND ac.id = cf.community_id) AS subscribed
FROM
user_ u
CROSS JOIN all_community ac
UNION ALL
SELECT
ac.*,
NULL AS user_id,
NULL AS subscribed
FROM
all_community ac;
CREATE VIEW community_mview AS
with all_community AS (
SELECT
ca.*
FROM
community_aggregates_mview ca
)
SELECT
ac.*,
u.id AS user_id,
(
SELECT
cf.id::boolean
FROM
community_follower cf
WHERE
u.id = cf.user_id
AND ac.id = cf.community_id) AS subscribed
FROM
user_ u
CROSS JOIN all_community ac
UNION ALL
SELECT
ac.*,
NULL AS user_id,
NULL AS subscribed
FROM
all_community ac;

View file

@ -1,3 +1,15 @@
DROP VIEW post_mview;
DROP MATERIALIZED VIEW post_aggregates_mview;
DROP VIEW post_view, post_aggregates_view;
DROP VIEW user_mention_view, comment_view, user_mention_mview, reply_view, comment_mview;
DROP MATERIALIZED VIEW comment_aggregates_mview;
DROP VIEW comment_aggregates_view;
ALTER TABLE post
DROP COLUMN ap_id,
DROP COLUMN local;
@ -6,3 +18,526 @@ ALTER TABLE comment
DROP COLUMN ap_id,
DROP COLUMN local;
-- Views are the same as before, except `*` does not reference the dropped columns
CREATE VIEW post_aggregates_view AS
SELECT
p.*,
(
SELECT
u.banned
FROM
user_ u
WHERE
p.creator_id = u.id) AS banned,
(
SELECT
cb.id::bool
FROM
community_user_ban cb
WHERE
p.creator_id = cb.user_id
AND p.community_id = cb.community_id) AS banned_from_community,
(
SELECT
name
FROM
user_
WHERE
p.creator_id = user_.id) AS creator_name,
(
SELECT
avatar
FROM
user_
WHERE
p.creator_id = user_.id) AS creator_avatar,
(
SELECT
name
FROM
community
WHERE
p.community_id = community.id) AS community_name,
(
SELECT
removed
FROM
community c
WHERE
p.community_id = c.id) AS community_removed,
(
SELECT
deleted
FROM
community c
WHERE
p.community_id = c.id) AS community_deleted,
(
SELECT
nsfw
FROM
community c
WHERE
p.community_id = c.id) AS community_nsfw,
(
SELECT
count(*)
FROM
comment
WHERE
comment.post_id = p.id) AS number_of_comments,
coalesce(sum(pl.score), 0) AS score,
count(
CASE WHEN pl.score = 1 THEN
1
ELSE
NULL
END) AS upvotes,
count(
CASE WHEN pl.score = - 1 THEN
1
ELSE
NULL
END) AS downvotes,
hot_rank (coalesce(sum(pl.score), 0), (
CASE WHEN (p.published < ('now'::timestamp - '1 month'::interval)) THEN
p.published -- Prevents necro-bumps
ELSE
greatest (c.recent_comment_time, p.published)
END)) AS hot_rank,
(
CASE WHEN (p.published < ('now'::timestamp - '1 month'::interval)) THEN
p.published -- Prevents necro-bumps
ELSE
greatest (c.recent_comment_time, p.published)
END) AS newest_activity_time
FROM
post p
LEFT JOIN post_like pl ON p.id = pl.post_id
LEFT JOIN (
SELECT
post_id,
max(published) AS recent_comment_time
FROM
comment
GROUP BY
1) c ON p.id = c.post_id
GROUP BY
p.id,
c.recent_comment_time;
CREATE VIEW post_view AS
with all_post AS (
SELECT
pa.*
FROM
post_aggregates_view pa
)
SELECT
ap.*,
u.id AS user_id,
coalesce(pl.score, 0) AS my_vote,
(
SELECT
cf.id::bool
FROM
community_follower cf
WHERE
u.id = cf.user_id
AND cf.community_id = ap.community_id) AS subscribed,
(
SELECT
pr.id::bool
FROM
post_read pr
WHERE
u.id = pr.user_id
AND pr.post_id = ap.id) AS read,
(
SELECT
ps.id::bool
FROM
post_saved ps
WHERE
u.id = ps.user_id
AND ps.post_id = ap.id) AS saved
FROM
user_ u
CROSS JOIN all_post ap
LEFT JOIN post_like pl ON u.id = pl.user_id
AND ap.id = pl.post_id
UNION ALL
SELECT
ap.*,
NULL AS user_id,
NULL AS my_vote,
NULL AS subscribed,
NULL AS read,
NULL AS saved
FROM
all_post ap;
CREATE MATERIALIZED VIEW post_aggregates_mview AS
SELECT
*
FROM
post_aggregates_view;
CREATE UNIQUE INDEX idx_post_aggregates_mview_id ON post_aggregates_mview (id);
CREATE VIEW post_mview AS
with all_post AS (
SELECT
pa.*
FROM
post_aggregates_mview pa
)
SELECT
ap.*,
u.id AS user_id,
coalesce(pl.score, 0) AS my_vote,
(
SELECT
cf.id::bool
FROM
community_follower cf
WHERE
u.id = cf.user_id
AND cf.community_id = ap.community_id) AS subscribed,
(
SELECT
pr.id::bool
FROM
post_read pr
WHERE
u.id = pr.user_id
AND pr.post_id = ap.id) AS read,
(
SELECT
ps.id::bool
FROM
post_saved ps
WHERE
u.id = ps.user_id
AND ps.post_id = ap.id) AS saved
FROM
user_ u
CROSS JOIN all_post ap
LEFT JOIN post_like pl ON u.id = pl.user_id
AND ap.id = pl.post_id
UNION ALL
SELECT
ap.*,
NULL AS user_id,
NULL AS my_vote,
NULL AS subscribed,
NULL AS read,
NULL AS saved
FROM
all_post ap;
CREATE VIEW comment_aggregates_view AS
SELECT
c.*,
(
SELECT
community_id
FROM
post p
WHERE
p.id = c.post_id), (
SELECT
co.name
FROM
post p,
community co
WHERE
p.id = c.post_id
AND p.community_id = co.id) AS community_name,
(
SELECT
u.banned
FROM
user_ u
WHERE
c.creator_id = u.id) AS banned,
(
SELECT
cb.id::bool
FROM
community_user_ban cb,
post p
WHERE
c.creator_id = cb.user_id
AND p.id = c.post_id
AND p.community_id = cb.community_id) AS banned_from_community,
(
SELECT
name
FROM
user_
WHERE
c.creator_id = user_.id) AS creator_name,
(
SELECT
avatar
FROM
user_
WHERE
c.creator_id = user_.id) AS creator_avatar,
coalesce(sum(cl.score), 0) AS score,
count(
CASE WHEN cl.score = 1 THEN
1
ELSE
NULL
END) AS upvotes,
count(
CASE WHEN cl.score = - 1 THEN
1
ELSE
NULL
END) AS downvotes,
hot_rank (coalesce(sum(cl.score), 0), c.published) AS hot_rank
FROM
comment c
LEFT JOIN comment_like cl ON c.id = cl.comment_id
GROUP BY
c.id;
CREATE MATERIALIZED VIEW comment_aggregates_mview AS
SELECT
*
FROM
comment_aggregates_view;
CREATE UNIQUE INDEX idx_comment_aggregates_mview_id ON comment_aggregates_mview (id);
CREATE VIEW comment_mview AS
with all_comment AS (
SELECT
ca.*
FROM
comment_aggregates_mview ca
)
SELECT
ac.*,
u.id AS user_id,
coalesce(cl.score, 0) AS my_vote,
(
SELECT
cf.id::boolean
FROM
community_follower cf
WHERE
u.id = cf.user_id
AND ac.community_id = cf.community_id) AS subscribed,
(
SELECT
cs.id::bool
FROM
comment_saved cs
WHERE
u.id = cs.user_id
AND cs.comment_id = ac.id) AS saved
FROM
user_ u
CROSS JOIN all_comment ac
LEFT JOIN comment_like cl ON u.id = cl.user_id
AND ac.id = cl.comment_id
UNION ALL
SELECT
ac.*,
NULL AS user_id,
NULL AS my_vote,
NULL AS subscribed,
NULL AS saved
FROM
all_comment ac;
CREATE VIEW reply_view AS
with closereply AS (
SELECT
c2.id,
c2.creator_id AS sender_id,
c.creator_id AS recipient_id
FROM
comment c
INNER JOIN comment c2 ON c.id = c2.parent_id
WHERE
c2.creator_id != c.creator_id
-- Do union where post is null
UNION
SELECT
c.id,
c.creator_id AS sender_id,
p.creator_id AS recipient_id
FROM
comment c,
post p
WHERE
c.post_id = p.id
AND c.parent_id IS NULL
AND c.creator_id != p.creator_id
)
SELECT
cv.*,
closereply.recipient_id
FROM
comment_mview cv,
closereply
WHERE
closereply.id = cv.id;
CREATE VIEW user_mention_mview AS
with all_comment AS (
SELECT
ca.*
FROM
comment_aggregates_mview ca
)
SELECT
ac.id,
um.id AS user_mention_id,
ac.creator_id,
ac.post_id,
ac.parent_id,
ac.content,
ac.removed,
um.read,
ac.published,
ac.updated,
ac.deleted,
ac.community_id,
ac.community_name,
ac.banned,
ac.banned_from_community,
ac.creator_name,
ac.creator_avatar,
ac.score,
ac.upvotes,
ac.downvotes,
ac.hot_rank,
u.id AS user_id,
coalesce(cl.score, 0) AS my_vote,
(
SELECT
cs.id::bool
FROM
comment_saved cs
WHERE
u.id = cs.user_id
AND cs.comment_id = ac.id) AS saved,
um.recipient_id
FROM
user_ u
CROSS JOIN all_comment ac
LEFT JOIN comment_like cl ON u.id = cl.user_id
AND ac.id = cl.comment_id
LEFT JOIN user_mention um ON um.comment_id = ac.id
UNION ALL
SELECT
ac.id,
um.id AS user_mention_id,
ac.creator_id,
ac.post_id,
ac.parent_id,
ac.content,
ac.removed,
um.read,
ac.published,
ac.updated,
ac.deleted,
ac.community_id,
ac.community_name,
ac.banned,
ac.banned_from_community,
ac.creator_name,
ac.creator_avatar,
ac.score,
ac.upvotes,
ac.downvotes,
ac.hot_rank,
NULL AS user_id,
NULL AS my_vote,
NULL AS saved,
um.recipient_id
FROM
all_comment ac
LEFT JOIN user_mention um ON um.comment_id = ac.id;
CREATE VIEW comment_view AS
with all_comment AS (
SELECT
ca.*
FROM
comment_aggregates_view ca
)
SELECT
ac.*,
u.id AS user_id,
coalesce(cl.score, 0) AS my_vote,
(
SELECT
cf.id::boolean
FROM
community_follower cf
WHERE
u.id = cf.user_id
AND ac.community_id = cf.community_id) AS subscribed,
(
SELECT
cs.id::bool
FROM
comment_saved cs
WHERE
u.id = cs.user_id
AND cs.comment_id = ac.id) AS saved
FROM
user_ u
CROSS JOIN all_comment ac
LEFT JOIN comment_like cl ON u.id = cl.user_id
AND ac.id = cl.comment_id
UNION ALL
SELECT
ac.*,
NULL AS user_id,
NULL AS my_vote,
NULL AS subscribed,
NULL AS saved
FROM
all_comment ac;
CREATE VIEW user_mention_view AS
SELECT
c.id,
um.id AS user_mention_id,
c.creator_id,
c.post_id,
c.parent_id,
c.content,
c.removed,
um.read,
c.published,
c.updated,
c.deleted,
c.community_id,
c.community_name,
c.banned,
c.banned_from_community,
c.creator_name,
c.creator_avatar,
c.score,
c.upvotes,
c.downvotes,
c.hot_rank,
c.user_id,
c.my_vote,
c.saved,
um.recipient_id
FROM
user_mention um,
comment_view c
WHERE
um.comment_id = c.id;

View file

@ -1,3 +1,5 @@
DROP VIEW user_alias_1, user_alias_2;
ALTER TABLE community
DROP COLUMN followers_url;
@ -13,3 +15,16 @@ ALTER TABLE user_
ALTER TABLE user_
DROP COLUMN shared_inbox_url;
-- Views are the same as before, except `*` does not reference the dropped columns
CREATE VIEW user_alias_1 AS
SELECT
*
FROM
user_;
CREATE VIEW user_alias_2 AS
SELECT
*
FROM
user_;

View file

@ -34,3 +34,5 @@ INSERT INTO category (name)
ALTER TABLE community
ADD category_id int REFERENCES category ON UPDATE CASCADE ON DELETE CASCADE NOT NULL DEFAULT 1;
CREATE INDEX idx_community_category ON community (category_id);

View file

@ -260,6 +260,8 @@ FROM
WHERE
lu.person_id = u.id;
CREATE UNIQUE INDEX idx_user_email_lower ON user_ (lower(email));
CREATE VIEW user_alias_1 AS
SELECT
*

View file

@ -1,6 +1,6 @@
ALTER TABLE post
DROP COLUMN embed_url;
DROP COLUMN embed_video_url;
ALTER TABLE post
ADD COLUMN embed_video_url text;
ADD COLUMN embed_html text;

View file

@ -65,3 +65,15 @@ CREATE TRIGGER post_aggregates_stickied
WHEN (OLD.stickied IS DISTINCT FROM NEW.stickied)
EXECUTE PROCEDURE post_aggregates_stickied ();
CREATE INDEX idx_post_aggregates_stickied_newest_comment_time ON post_aggregates (stickied DESC, newest_comment_time DESC);
CREATE INDEX idx_post_aggregates_stickied_comments ON post_aggregates (stickied DESC, comments DESC);
CREATE INDEX idx_post_aggregates_stickied_hot ON post_aggregates (stickied DESC, hot_rank (score, published) DESC, published DESC);
CREATE INDEX idx_post_aggregates_stickied_active ON post_aggregates (stickied DESC, hot_rank (score, newest_comment_time_necro) DESC, newest_comment_time_necro DESC);
CREATE INDEX idx_post_aggregates_stickied_score ON post_aggregates (stickied DESC, score DESC);
CREATE INDEX idx_post_aggregates_stickied_published ON post_aggregates (stickied DESC, published DESC);

View file

@ -8,7 +8,7 @@ CREATE INDEX idx_post_aggregates_comments ON post_aggregates (comments DESC);
CREATE INDEX idx_post_aggregates_hot ON post_aggregates (hot_rank (score, published) DESC, published DESC);
CREATE INDEX idx_post_aggregates_active ON post_aggregates (hot_rank (score, newest_comment_time) DESC, newest_comment_time DESC);
CREATE INDEX idx_post_aggregates_active ON post_aggregates (hot_rank (score, newest_comment_time_necro) DESC, newest_comment_time_necro DESC);
CREATE INDEX idx_post_aggregates_score ON post_aggregates (score DESC);

View file

@ -1,3 +1,6 @@
ALTER TABLE local_user
ALTER default_sort_type DROP DEFAULT;
-- update the default sort type
UPDATE
local_user
@ -29,6 +32,9 @@ ALTER TABLE local_user
ALTER COLUMN default_sort_type TYPE sort_type_enum
USING default_sort_type::text::sort_type_enum;
ALTER TABLE local_user
ALTER default_sort_type SET DEFAULT 'Active';
-- drop the old enum
DROP TYPE sort_type_enum__;

View file

@ -1,3 +1,6 @@
ALTER TABLE local_user
ALTER default_sort_type DROP DEFAULT;
-- update the default sort type
UPDATE
local_user
@ -32,6 +35,9 @@ ALTER TABLE local_user
ALTER COLUMN default_sort_type TYPE sort_type_enum
USING default_sort_type::text::sort_type_enum;
ALTER TABLE local_user
ALTER default_sort_type SET DEFAULT 'Active';
-- drop the old enum
DROP TYPE sort_type_enum__;

View file

@ -26,3 +26,5 @@ DROP TABLE sent_activity;
DROP TABLE received_activity;
CREATE UNIQUE INDEX idx_activity_ap_id ON activity (ap_id);

View file

@ -14,3 +14,7 @@ WHERE
ALTER TABLE local_user
DROP COLUMN admin;
CREATE INDEX idx_person_admin ON person (admin)
WHERE
admin;

View file

@ -328,7 +328,9 @@ ALTER TABLE captcha_answer
ALTER COLUMN published TYPE timestamp
USING published;
CREATE OR REPLACE FUNCTION hot_rank (score numeric, published timestamp without time zone)
DROP FUNCTION hot_rank;
CREATE FUNCTION hot_rank (score numeric, published timestamp without time zone)
RETURNS integer
AS $$
DECLARE

View file

@ -129,13 +129,11 @@ pub async fn start_lemmy_server(args: CmdArgs) -> LemmyResult<()> {
println!("Lemmy v{VERSION}");
if let Some(CmdSubcommand::Migration { subcommand }) = args.subcommand {
let mut options = schema_setup::Options::default();
let options = match subcommand {
MigrationSubcommand::Run => schema_setup::Options::default(),
};
match subcommand {
MigrationSubcommand::Run => {}
}
schema_setup::run(&SETTINGS.get_database_url(), &options)?;
schema_setup::run(&SETTINGS.get_database_url(), options)?;
return Ok(());
}