mirror of
https://github.com/LemmyNet/lemmy.git
synced 2024-12-27 21:31:31 +00:00
fix
This commit is contained in:
parent
614e0e59d1
commit
5d8af0d476
20 changed files with 3127 additions and 115 deletions
|
@ -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:
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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,47 +155,62 @@ 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 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(wrapper.conn)?;
|
||||
|
||||
debug_assert_eq!(num_rows_updated, 1);
|
||||
}
|
||||
|
||||
// Run replaceable_schema
|
||||
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)?;
|
||||
|
||||
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(())
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -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_;
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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
|
||||
*
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
SELECT
|
||||
1;
|
||||
|
|
@ -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__;
|
||||
|
||||
|
|
|
@ -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__;
|
||||
|
||||
|
|
|
@ -26,3 +26,5 @@ DROP TABLE sent_activity;
|
|||
|
||||
DROP TABLE received_activity;
|
||||
|
||||
CREATE UNIQUE INDEX idx_activity_ap_id ON activity (ap_id);
|
||||
|
||||
|
|
|
@ -14,3 +14,7 @@ WHERE
|
|||
ALTER TABLE local_user
|
||||
DROP COLUMN admin;
|
||||
|
||||
CREATE INDEX idx_person_admin ON person (admin)
|
||||
WHERE
|
||||
admin;
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
10
src/lib.rs
10
src/lib.rs
|
@ -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(());
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue