mirror of
https://github.com/LemmyNet/lemmy.git
synced 2025-09-03 11:43:51 +00:00
Use a new separate crate for uplete (#5848)
* replace lemmy_db_schema::utils::uplete with in-progress crate * use published diesel-uplete crate * remove unused tuplex dependency * move diesel-uplete to dev-dependencies for lemmy_db_views_post * import table modules in table_impls.rs * update to new diesel-uplete with renamed stuff
This commit is contained in:
parent
fe30f30c4e
commit
21c1784a01
18 changed files with 181 additions and 557 deletions
45
Cargo.lock
generated
45
Cargo.lock
generated
|
@ -756,7 +756,7 @@ dependencies = [
|
|||
"cap-primitives",
|
||||
"cap-std",
|
||||
"io-lifetimes",
|
||||
"windows-sys 0.52.0",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -773,7 +773,7 @@ dependencies = [
|
|||
"maybe-owned",
|
||||
"rustix 1.0.7",
|
||||
"rustix-linux-procfs",
|
||||
"windows-sys 0.52.0",
|
||||
"windows-sys 0.59.0",
|
||||
"winx",
|
||||
]
|
||||
|
||||
|
@ -1592,6 +1592,16 @@ dependencies = [
|
|||
"syn 2.0.102",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "diesel-uplete"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4712bc03fc34bda37e1e46a7028cc444e2488a63cffdd2fe79f3db227e6900d8"
|
||||
dependencies = [
|
||||
"diesel",
|
||||
"tuplex",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "diesel_derives"
|
||||
version = "2.2.5"
|
||||
|
@ -1894,7 +1904,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "cea14ef9355e3beab063703aa9dab15afd25f0667c341310c1e5274bb1d0da18"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"windows-sys 0.52.0",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -2016,7 +2026,7 @@ checksum = "0ce92ff622d6dadf7349484f42c93271a0d49b7cc4d466a936405bacbe10aa78"
|
|||
dependencies = [
|
||||
"cfg-if",
|
||||
"rustix 1.0.7",
|
||||
"windows-sys 0.52.0",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -2079,7 +2089,7 @@ checksum = "94e7099f6313ecacbe1256e8ff9d617b75d1bcb16a6fddef94866d225a01a14a"
|
|||
dependencies = [
|
||||
"io-lifetimes",
|
||||
"rustix 1.0.7",
|
||||
"windows-sys 0.52.0",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -2926,7 +2936,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "2285ddfe3054097ef4b2fe909ef8c3bcd1ea52a8f0d274416caebeef39f04a65"
|
||||
dependencies = [
|
||||
"io-lifetimes",
|
||||
"windows-sys 0.52.0",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -3332,6 +3342,7 @@ dependencies = [
|
|||
"diesel",
|
||||
"diesel-async",
|
||||
"diesel-derive-newtype",
|
||||
"diesel-uplete",
|
||||
"diesel_ltree",
|
||||
"futures-util",
|
||||
"i-love-jesus",
|
||||
|
@ -3352,7 +3363,6 @@ dependencies = [
|
|||
"tokio-postgres-rustls",
|
||||
"tracing",
|
||||
"ts-rs",
|
||||
"tuplex",
|
||||
"url",
|
||||
"uuid",
|
||||
]
|
||||
|
@ -3363,6 +3373,7 @@ version = "1.0.0-alpha.5"
|
|||
dependencies = [
|
||||
"diesel",
|
||||
"diesel-derive-enum",
|
||||
"diesel-uplete",
|
||||
"diesel_ltree",
|
||||
"serde",
|
||||
"strum",
|
||||
|
@ -3649,6 +3660,7 @@ dependencies = [
|
|||
"chrono",
|
||||
"diesel",
|
||||
"diesel-async",
|
||||
"diesel-uplete",
|
||||
"i-love-jesus",
|
||||
"lemmy_db_schema",
|
||||
"lemmy_db_schema_file",
|
||||
|
@ -3875,6 +3887,7 @@ dependencies = [
|
|||
"clokwerk",
|
||||
"diesel",
|
||||
"diesel-async",
|
||||
"diesel-uplete",
|
||||
"futures",
|
||||
"futures-util",
|
||||
"http 1.3.1",
|
||||
|
@ -4015,7 +4028,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "07033963ba89ebaf1584d767badaa2e8fcec21aedea6b8c0346d487d49c28667"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"windows-targets 0.48.5",
|
||||
"windows-targets 0.52.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -5213,7 +5226,7 @@ dependencies = [
|
|||
"once_cell",
|
||||
"socket2",
|
||||
"tracing",
|
||||
"windows-sys 0.52.0",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -5663,7 +5676,7 @@ dependencies = [
|
|||
"errno",
|
||||
"libc",
|
||||
"linux-raw-sys 0.4.15",
|
||||
"windows-sys 0.52.0",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -5676,7 +5689,7 @@ dependencies = [
|
|||
"errno",
|
||||
"libc",
|
||||
"linux-raw-sys 0.9.4",
|
||||
"windows-sys 0.52.0",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -6142,7 +6155,7 @@ dependencies = [
|
|||
"cfg-if",
|
||||
"libc",
|
||||
"psm",
|
||||
"windows-sys 0.52.0",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -6336,7 +6349,7 @@ dependencies = [
|
|||
"fd-lock",
|
||||
"io-lifetimes",
|
||||
"rustix 0.38.44",
|
||||
"windows-sys 0.52.0",
|
||||
"windows-sys 0.59.0",
|
||||
"winx",
|
||||
]
|
||||
|
||||
|
@ -6362,7 +6375,7 @@ dependencies = [
|
|||
"getrandom 0.3.3",
|
||||
"once_cell",
|
||||
"rustix 1.0.7",
|
||||
"windows-sys 0.52.0",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -7730,7 +7743,7 @@ version = "0.1.9"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb"
|
||||
dependencies = [
|
||||
"windows-sys 0.48.0",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -8042,7 +8055,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "3f3fd376f71958b862e7afb20cfe5a22830e1963462f3a17f49d82a6c1d1f42d"
|
||||
dependencies = [
|
||||
"bitflags 2.9.1",
|
||||
"windows-sys 0.52.0",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
|
@ -215,7 +215,6 @@ i-love-jesus = { version = "0.2.0" }
|
|||
clap = { version = "4.5.39", features = ["derive", "env"] }
|
||||
pretty_assertions = "1.4.1"
|
||||
derive-new = "0.7.0"
|
||||
tuplex = "0.1.2"
|
||||
html2text = "0.15.1"
|
||||
async-trait = "0.1.88"
|
||||
either = { version = "1.15.0", features = ["serde"] }
|
||||
|
@ -226,6 +225,7 @@ extism = { git = "https://github.com/extism/extism.git", branch = "main", defaul
|
|||
] }
|
||||
extism-convert = { git = "https://github.com/extism/extism.git", branch = "main" }
|
||||
unified-diff = "0.2.1"
|
||||
diesel-uplete = { version = "0.2.0" }
|
||||
|
||||
[dependencies]
|
||||
lemmy_api = { workspace = true }
|
||||
|
|
|
@ -29,13 +29,13 @@ full = [
|
|||
"serde_json",
|
||||
"diesel_ltree",
|
||||
"diesel-async",
|
||||
"diesel-uplete",
|
||||
"deadpool",
|
||||
"tokio",
|
||||
"tokio-postgres",
|
||||
"tokio-postgres-rustls",
|
||||
"rustls",
|
||||
"i-love-jesus",
|
||||
"tuplex",
|
||||
"moka",
|
||||
"lemmy_db_schema_file/full",
|
||||
"lemmy_db_schema_setup",
|
||||
|
@ -60,6 +60,7 @@ diesel-async = { workspace = true, features = [
|
|||
"deadpool",
|
||||
"postgres",
|
||||
], optional = true }
|
||||
diesel-uplete = { workspace = true, optional = true }
|
||||
regex = { workspace = true, optional = true }
|
||||
diesel_ltree = { workspace = true, optional = true }
|
||||
tracing = { workspace = true }
|
||||
|
@ -73,7 +74,6 @@ rustls = { workspace = true, optional = true }
|
|||
uuid.workspace = true
|
||||
i-love-jesus = { workspace = true, optional = true }
|
||||
derive-new.workspace = true
|
||||
tuplex = { workspace = true, optional = true }
|
||||
moka = { workspace = true, optional = true }
|
||||
|
||||
|
||||
|
|
|
@ -13,7 +13,6 @@ use crate::{
|
|||
utils::{
|
||||
functions::{coalesce, hot_rank},
|
||||
get_conn,
|
||||
uplete,
|
||||
validate_like,
|
||||
DbPool,
|
||||
DELETED_REPLACEMENT_TEXT,
|
||||
|
@ -30,6 +29,7 @@ use diesel::{
|
|||
};
|
||||
use diesel_async::RunQueryDsl;
|
||||
use diesel_ltree::Ltree;
|
||||
use diesel_uplete::{uplete, UpleteCount};
|
||||
use lemmy_db_schema_file::schema::{comment, comment_actions, community, post};
|
||||
use lemmy_utils::{
|
||||
error::{LemmyErrorExt, LemmyErrorExt2, LemmyErrorType, LemmyResult},
|
||||
|
@ -287,9 +287,9 @@ impl Likeable for CommentActions {
|
|||
pool: &mut DbPool<'_>,
|
||||
person_id: PersonId,
|
||||
comment_id: Self::IdType,
|
||||
) -> LemmyResult<uplete::Count> {
|
||||
) -> LemmyResult<UpleteCount> {
|
||||
let conn = &mut get_conn(pool).await?;
|
||||
uplete::new(comment_actions::table.find((person_id, comment_id)))
|
||||
uplete(comment_actions::table.find((person_id, comment_id)))
|
||||
.set_null(comment_actions::like_score)
|
||||
.set_null(comment_actions::liked_at)
|
||||
.get_result(conn)
|
||||
|
@ -300,10 +300,10 @@ impl Likeable for CommentActions {
|
|||
async fn remove_all_likes(
|
||||
pool: &mut DbPool<'_>,
|
||||
creator_id: PersonId,
|
||||
) -> LemmyResult<uplete::Count> {
|
||||
) -> LemmyResult<UpleteCount> {
|
||||
let conn = &mut get_conn(pool).await?;
|
||||
|
||||
uplete::new(comment_actions::table.filter(comment_actions::person_id.eq(creator_id)))
|
||||
uplete(comment_actions::table.filter(comment_actions::person_id.eq(creator_id)))
|
||||
.set_null(comment_actions::like_score)
|
||||
.set_null(comment_actions::liked_at)
|
||||
.get_result(conn)
|
||||
|
@ -315,20 +315,18 @@ impl Likeable for CommentActions {
|
|||
pool: &mut DbPool<'_>,
|
||||
creator_id: PersonId,
|
||||
community_id: CommunityId,
|
||||
) -> LemmyResult<uplete::Count> {
|
||||
) -> LemmyResult<UpleteCount> {
|
||||
let comment_ids =
|
||||
Comment::creator_comment_ids_in_community(pool, creator_id, community_id).await?;
|
||||
|
||||
let conn = &mut get_conn(pool).await?;
|
||||
|
||||
uplete::new(
|
||||
comment_actions::table.filter(comment_actions::comment_id.eq_any(comment_ids.clone())),
|
||||
)
|
||||
.set_null(comment_actions::like_score)
|
||||
.set_null(comment_actions::liked_at)
|
||||
.get_result(conn)
|
||||
.await
|
||||
.with_lemmy_type(LemmyErrorType::CouldntUpdateComment)
|
||||
uplete(comment_actions::table.filter(comment_actions::comment_id.eq_any(comment_ids.clone())))
|
||||
.set_null(comment_actions::like_score)
|
||||
.set_null(comment_actions::liked_at)
|
||||
.get_result(conn)
|
||||
.await
|
||||
.with_lemmy_type(LemmyErrorType::CouldntUpdateComment)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -346,9 +344,9 @@ impl Saveable for CommentActions {
|
|||
.await
|
||||
.with_lemmy_type(LemmyErrorType::CouldntSaveComment)
|
||||
}
|
||||
async fn unsave(pool: &mut DbPool<'_>, form: &Self::Form) -> LemmyResult<uplete::Count> {
|
||||
async fn unsave(pool: &mut DbPool<'_>, form: &Self::Form) -> LemmyResult<UpleteCount> {
|
||||
let conn = &mut get_conn(pool).await?;
|
||||
uplete::new(comment_actions::table.find((form.person_id, form.comment_id)))
|
||||
uplete(comment_actions::table.find((form.person_id, form.comment_id)))
|
||||
.set_null(comment_actions::saved_at)
|
||||
.get_result(conn)
|
||||
.await
|
||||
|
@ -385,7 +383,7 @@ mod tests {
|
|||
post::{Post, PostInsertForm},
|
||||
},
|
||||
traits::{Crud, Likeable, Saveable},
|
||||
utils::{build_db_pool_for_tests, uplete, RANK_DEFAULT},
|
||||
utils::{build_db_pool_for_tests, RANK_DEFAULT},
|
||||
};
|
||||
use diesel_ltree::Ltree;
|
||||
use lemmy_utils::error::LemmyResult;
|
||||
|
@ -499,8 +497,8 @@ mod tests {
|
|||
format!("0.{}.{}", expected_comment.id, inserted_child_comment.id),
|
||||
inserted_child_comment.path.0,
|
||||
);
|
||||
assert_eq!(uplete::Count::only_updated(1), like_removed);
|
||||
assert_eq!(uplete::Count::only_deleted(1), saved_removed);
|
||||
assert_eq!(UpleteCount::only_updated(1), like_removed);
|
||||
assert_eq!(UpleteCount::only_deleted(1), saved_removed);
|
||||
assert_eq!(1, num_deleted);
|
||||
|
||||
Ok(())
|
||||
|
|
|
@ -20,7 +20,6 @@ use crate::{
|
|||
format_actor_url,
|
||||
functions::{coalesce, coalesce_2_nullable, lower, random_smallint},
|
||||
get_conn,
|
||||
uplete,
|
||||
DbPool,
|
||||
},
|
||||
};
|
||||
|
@ -36,6 +35,7 @@ use diesel::{
|
|||
QueryDsl,
|
||||
};
|
||||
use diesel_async::RunQueryDsl;
|
||||
use diesel_uplete::{uplete, UpleteCount};
|
||||
use lemmy_db_schema_file::{
|
||||
enums::{CommunityFollowerState, CommunityVisibility, ListingType},
|
||||
schema::{comment, community, community_actions, instance, post},
|
||||
|
@ -101,9 +101,9 @@ impl Joinable for CommunityActions {
|
|||
.with_lemmy_type(LemmyErrorType::CommunityModeratorAlreadyExists)
|
||||
}
|
||||
|
||||
async fn leave(pool: &mut DbPool<'_>, form: &Self::Form) -> LemmyResult<uplete::Count> {
|
||||
async fn leave(pool: &mut DbPool<'_>, form: &Self::Form) -> LemmyResult<UpleteCount> {
|
||||
let conn = &mut get_conn(pool).await?;
|
||||
uplete::new(community_actions::table.find((form.person_id, form.community_id)))
|
||||
uplete(community_actions::table.find((form.person_id, form.community_id)))
|
||||
.set_null(community_actions::became_moderator_at)
|
||||
.get_result(conn)
|
||||
.await
|
||||
|
@ -311,24 +311,22 @@ impl CommunityActions {
|
|||
pub async fn delete_mods_for_community(
|
||||
pool: &mut DbPool<'_>,
|
||||
for_community_id: CommunityId,
|
||||
) -> LemmyResult<uplete::Count> {
|
||||
) -> LemmyResult<UpleteCount> {
|
||||
let conn = &mut get_conn(pool).await?;
|
||||
|
||||
uplete::new(
|
||||
community_actions::table.filter(community_actions::community_id.eq(for_community_id)),
|
||||
)
|
||||
.set_null(community_actions::became_moderator_at)
|
||||
.get_result(conn)
|
||||
.await
|
||||
.with_lemmy_type(LemmyErrorType::NotFound)
|
||||
uplete(community_actions::table.filter(community_actions::community_id.eq(for_community_id)))
|
||||
.set_null(community_actions::became_moderator_at)
|
||||
.get_result(conn)
|
||||
.await
|
||||
.with_lemmy_type(LemmyErrorType::NotFound)
|
||||
}
|
||||
|
||||
pub async fn leave_mod_team_for_all_communities(
|
||||
pool: &mut DbPool<'_>,
|
||||
for_person_id: PersonId,
|
||||
) -> LemmyResult<uplete::Count> {
|
||||
) -> LemmyResult<UpleteCount> {
|
||||
let conn = &mut get_conn(pool).await?;
|
||||
uplete::new(community_actions::table.filter(community_actions::person_id.eq(for_person_id)))
|
||||
uplete(community_actions::table.filter(community_actions::person_id.eq(for_person_id)))
|
||||
.set_null(community_actions::became_moderator_at)
|
||||
.get_result(conn)
|
||||
.await
|
||||
|
@ -475,9 +473,9 @@ impl Bannable for CommunityActions {
|
|||
.with_lemmy_type(LemmyErrorType::CommunityUserAlreadyBanned)
|
||||
}
|
||||
|
||||
async fn unban(pool: &mut DbPool<'_>, form: &Self::Form) -> LemmyResult<uplete::Count> {
|
||||
async fn unban(pool: &mut DbPool<'_>, form: &Self::Form) -> LemmyResult<UpleteCount> {
|
||||
let conn = &mut get_conn(pool).await?;
|
||||
uplete::new(community_actions::table.find((form.person_id, form.community_id)))
|
||||
uplete(community_actions::table.find((form.person_id, form.community_id)))
|
||||
.set_null(community_actions::received_ban_at)
|
||||
.set_null(community_actions::ban_expires_at)
|
||||
.get_result(conn)
|
||||
|
@ -526,9 +524,9 @@ impl Followable for CommunityActions {
|
|||
pool: &mut DbPool<'_>,
|
||||
person_id: PersonId,
|
||||
community_id: Self::IdType,
|
||||
) -> LemmyResult<uplete::Count> {
|
||||
) -> LemmyResult<UpleteCount> {
|
||||
let conn = &mut get_conn(pool).await?;
|
||||
uplete::new(community_actions::table.find((person_id, community_id)))
|
||||
uplete(community_actions::table.find((person_id, community_id)))
|
||||
.set_null(community_actions::followed_at)
|
||||
.set_null(community_actions::follow_state)
|
||||
.set_null(community_actions::follow_approver_id)
|
||||
|
@ -561,9 +559,9 @@ impl Blockable for CommunityActions {
|
|||
async fn unblock(
|
||||
pool: &mut DbPool<'_>,
|
||||
community_block_form: &Self::Form,
|
||||
) -> LemmyResult<uplete::Count> {
|
||||
) -> LemmyResult<UpleteCount> {
|
||||
let conn = &mut get_conn(pool).await?;
|
||||
uplete::new(community_actions::table.find((
|
||||
uplete(community_actions::table.find((
|
||||
community_block_form.person_id,
|
||||
community_block_form.community_id,
|
||||
)))
|
||||
|
@ -696,7 +694,7 @@ mod tests {
|
|||
post::{Post, PostInsertForm},
|
||||
},
|
||||
traits::{Bannable, Crud, Followable, Joinable},
|
||||
utils::{build_db_pool_for_tests, uplete, RANK_DEFAULT},
|
||||
utils::{build_db_pool_for_tests, RANK_DEFAULT},
|
||||
};
|
||||
use lemmy_utils::error::LemmyResult;
|
||||
use pretty_assertions::assert_eq;
|
||||
|
@ -854,9 +852,9 @@ mod tests {
|
|||
|
||||
assert_eq!(expected_community, read_community);
|
||||
assert_eq!(expected_community, updated_community);
|
||||
assert_eq!(uplete::Count::only_updated(1), ignored_community);
|
||||
assert_eq!(uplete::Count::only_updated(1), left_community);
|
||||
assert_eq!(uplete::Count::only_deleted(1), unban);
|
||||
assert_eq!(UpleteCount::only_updated(1), ignored_community);
|
||||
assert_eq!(UpleteCount::only_updated(1), left_community);
|
||||
assert_eq!(UpleteCount::only_deleted(1), unban);
|
||||
// assert_eq!(2, loaded_count);
|
||||
assert_eq!(1, num_deleted);
|
||||
|
||||
|
|
|
@ -10,7 +10,6 @@ use crate::{
|
|||
functions::{coalesce, lower},
|
||||
get_conn,
|
||||
now,
|
||||
uplete,
|
||||
DbPool,
|
||||
},
|
||||
};
|
||||
|
@ -24,6 +23,7 @@ use diesel::{
|
|||
SelectableHelper,
|
||||
};
|
||||
use diesel_async::RunQueryDsl;
|
||||
use diesel_uplete::{uplete, UpleteCount};
|
||||
use lemmy_db_schema_file::schema::{
|
||||
federation_allowlist,
|
||||
federation_blocklist,
|
||||
|
@ -224,9 +224,9 @@ impl Blockable for InstanceActions {
|
|||
.with_lemmy_type(LemmyErrorType::InstanceBlockAlreadyExists)
|
||||
}
|
||||
|
||||
async fn unblock(pool: &mut DbPool<'_>, form: &Self::Form) -> LemmyResult<uplete::Count> {
|
||||
async fn unblock(pool: &mut DbPool<'_>, form: &Self::Form) -> LemmyResult<UpleteCount> {
|
||||
let conn = &mut get_conn(pool).await?;
|
||||
uplete::new(instance_actions::table.find((form.person_id, form.instance_id)))
|
||||
uplete(instance_actions::table.find((form.person_id, form.instance_id)))
|
||||
.set_null(instance_actions::blocked_at)
|
||||
.get_result(conn)
|
||||
.await
|
||||
|
@ -304,10 +304,10 @@ impl Bannable for InstanceActions {
|
|||
.await?,
|
||||
)
|
||||
}
|
||||
async fn unban(pool: &mut DbPool<'_>, form: &Self::Form) -> LemmyResult<uplete::Count> {
|
||||
async fn unban(pool: &mut DbPool<'_>, form: &Self::Form) -> LemmyResult<UpleteCount> {
|
||||
let conn = &mut get_conn(pool).await?;
|
||||
Ok(
|
||||
uplete::new(instance_actions::table.find((form.person_id, form.instance_id)))
|
||||
uplete(instance_actions::table.find((form.person_id, form.instance_id)))
|
||||
.set_null(instance_actions::received_ban_at)
|
||||
.set_null(instance_actions::ban_expires_at)
|
||||
.get_result(conn)
|
||||
|
|
|
@ -11,7 +11,7 @@ use crate::{
|
|||
PersonUpdateForm,
|
||||
},
|
||||
traits::{ApubActor, Blockable, Crud, Followable},
|
||||
utils::{format_actor_url, functions::lower, get_conn, uplete, DbPool},
|
||||
utils::{format_actor_url, functions::lower, get_conn, DbPool},
|
||||
};
|
||||
use chrono::Utc;
|
||||
use diesel::{
|
||||
|
@ -22,6 +22,7 @@ use diesel::{
|
|||
QueryDsl,
|
||||
};
|
||||
use diesel_async::RunQueryDsl;
|
||||
use diesel_uplete::{uplete, UpleteCount};
|
||||
use lemmy_db_schema_file::schema::{
|
||||
instance,
|
||||
instance_actions,
|
||||
|
@ -249,9 +250,9 @@ impl Followable for PersonActions {
|
|||
pool: &mut DbPool<'_>,
|
||||
person_id: PersonId,
|
||||
target_id: Self::IdType,
|
||||
) -> LemmyResult<uplete::Count> {
|
||||
) -> LemmyResult<UpleteCount> {
|
||||
let conn = &mut get_conn(pool).await?;
|
||||
uplete::new(person_actions::table.find((person_id, target_id)))
|
||||
uplete(person_actions::table.find((person_id, target_id)))
|
||||
.set_null(person_actions::followed_at)
|
||||
.set_null(person_actions::follow_pending)
|
||||
.get_result(conn)
|
||||
|
@ -278,9 +279,9 @@ impl Blockable for PersonActions {
|
|||
.with_lemmy_type(LemmyErrorType::PersonBlockAlreadyExists)
|
||||
}
|
||||
|
||||
async fn unblock(pool: &mut DbPool<'_>, form: &Self::Form) -> LemmyResult<uplete::Count> {
|
||||
async fn unblock(pool: &mut DbPool<'_>, form: &Self::Form) -> LemmyResult<UpleteCount> {
|
||||
let conn = &mut get_conn(pool).await?;
|
||||
uplete::new(person_actions::table.find((form.person_id, form.target_id)))
|
||||
uplete(person_actions::table.find((form.person_id, form.target_id)))
|
||||
.set_null(person_actions::blocked_at)
|
||||
.get_result(conn)
|
||||
.await
|
||||
|
@ -361,9 +362,9 @@ impl PersonActions {
|
|||
pool: &mut DbPool<'_>,
|
||||
person_id: PersonId,
|
||||
target_id: PersonId,
|
||||
) -> LemmyResult<uplete::Count> {
|
||||
) -> LemmyResult<UpleteCount> {
|
||||
let conn = &mut get_conn(pool).await?;
|
||||
uplete::new(person_actions::table.find((person_id, target_id)))
|
||||
uplete(person_actions::table.find((person_id, target_id)))
|
||||
.set_null(person_actions::note)
|
||||
.set_null(person_actions::noted_at)
|
||||
.get_result(conn)
|
||||
|
@ -462,8 +463,9 @@ mod tests {
|
|||
post::{Post, PostActions, PostInsertForm, PostLikeForm},
|
||||
},
|
||||
traits::{Crud, Followable, Likeable},
|
||||
utils::{build_db_pool_for_tests, uplete},
|
||||
utils::build_db_pool_for_tests,
|
||||
};
|
||||
use diesel_uplete::UpleteCount;
|
||||
use lemmy_utils::error::LemmyResult;
|
||||
use pretty_assertions::assert_eq;
|
||||
use serial_test::serial;
|
||||
|
@ -547,7 +549,7 @@ mod tests {
|
|||
|
||||
let unfollow =
|
||||
PersonActions::unfollow(pool, follow_form.person_id, follow_form.target_id).await?;
|
||||
assert_eq!(uplete::Count::only_deleted(1), unfollow);
|
||||
assert_eq!(UpleteCount::only_deleted(1), unfollow);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
@ -16,7 +16,6 @@ use crate::{
|
|||
functions::{coalesce, hot_rank, scaled_rank},
|
||||
get_conn,
|
||||
now,
|
||||
uplete,
|
||||
validate_like,
|
||||
DbPool,
|
||||
DELETED_REPLACEMENT_TEXT,
|
||||
|
@ -39,6 +38,7 @@ use diesel::{
|
|||
QueryDsl,
|
||||
};
|
||||
use diesel_async::RunQueryDsl;
|
||||
use diesel_uplete::{uplete, UpleteCount};
|
||||
use lemmy_db_schema_file::schema::{community, person, post, post_actions};
|
||||
use lemmy_utils::{
|
||||
error::{LemmyErrorExt, LemmyErrorExt2, LemmyErrorType, LemmyResult},
|
||||
|
@ -364,9 +364,9 @@ impl Likeable for PostActions {
|
|||
pool: &mut DbPool<'_>,
|
||||
person_id: PersonId,
|
||||
post_id: Self::IdType,
|
||||
) -> LemmyResult<uplete::Count> {
|
||||
) -> LemmyResult<UpleteCount> {
|
||||
let conn = &mut get_conn(pool).await?;
|
||||
uplete::new(post_actions::table.find((person_id, post_id)))
|
||||
uplete(post_actions::table.find((person_id, post_id)))
|
||||
.set_null(post_actions::like_score)
|
||||
.set_null(post_actions::liked_at)
|
||||
.get_result(conn)
|
||||
|
@ -377,10 +377,10 @@ impl Likeable for PostActions {
|
|||
async fn remove_all_likes(
|
||||
pool: &mut DbPool<'_>,
|
||||
person_id: PersonId,
|
||||
) -> LemmyResult<uplete::Count> {
|
||||
) -> LemmyResult<UpleteCount> {
|
||||
let conn = &mut get_conn(pool).await?;
|
||||
|
||||
uplete::new(post_actions::table.filter(post_actions::person_id.eq(person_id)))
|
||||
uplete(post_actions::table.filter(post_actions::person_id.eq(person_id)))
|
||||
.set_null(post_actions::like_score)
|
||||
.set_null(post_actions::liked_at)
|
||||
.get_result(conn)
|
||||
|
@ -392,12 +392,12 @@ impl Likeable for PostActions {
|
|||
pool: &mut DbPool<'_>,
|
||||
person_id: PersonId,
|
||||
community_id: CommunityId,
|
||||
) -> LemmyResult<uplete::Count> {
|
||||
) -> LemmyResult<UpleteCount> {
|
||||
let post_ids = Post::creator_post_ids_in_community(pool, person_id, community_id).await?;
|
||||
|
||||
let conn = &mut get_conn(pool).await?;
|
||||
|
||||
uplete::new(post_actions::table.filter(post_actions::post_id.eq_any(post_ids.clone())))
|
||||
uplete(post_actions::table.filter(post_actions::post_id.eq_any(post_ids.clone())))
|
||||
.set_null(post_actions::like_score)
|
||||
.set_null(post_actions::liked_at)
|
||||
.get_result(conn)
|
||||
|
@ -420,9 +420,9 @@ impl Saveable for PostActions {
|
|||
.await
|
||||
.with_lemmy_type(LemmyErrorType::CouldntSavePost)
|
||||
}
|
||||
async fn unsave(pool: &mut DbPool<'_>, form: &Self::Form) -> LemmyResult<uplete::Count> {
|
||||
async fn unsave(pool: &mut DbPool<'_>, form: &Self::Form) -> LemmyResult<UpleteCount> {
|
||||
let conn = &mut get_conn(pool).await?;
|
||||
uplete::new(post_actions::table.find((form.person_id, form.post_id)))
|
||||
uplete(post_actions::table.find((form.person_id, form.post_id)))
|
||||
.set_null(post_actions::saved_at)
|
||||
.get_result(conn)
|
||||
.await
|
||||
|
@ -437,10 +437,10 @@ impl Readable for PostActions {
|
|||
Self::mark_many_as_read(pool, std::slice::from_ref(form)).await
|
||||
}
|
||||
|
||||
async fn mark_as_unread(pool: &mut DbPool<'_>, form: &Self::Form) -> LemmyResult<uplete::Count> {
|
||||
async fn mark_as_unread(pool: &mut DbPool<'_>, form: &Self::Form) -> LemmyResult<UpleteCount> {
|
||||
let conn = &mut get_conn(pool).await?;
|
||||
|
||||
uplete::new(
|
||||
uplete(
|
||||
post_actions::table
|
||||
.filter(post_actions::post_id.eq(form.post_id))
|
||||
.filter(post_actions::person_id.eq(form.person_id)),
|
||||
|
@ -480,10 +480,10 @@ impl Hideable for PostActions {
|
|||
.with_lemmy_type(LemmyErrorType::CouldntHidePost)
|
||||
}
|
||||
|
||||
async fn unhide(pool: &mut DbPool<'_>, form: &Self::Form) -> LemmyResult<uplete::Count> {
|
||||
async fn unhide(pool: &mut DbPool<'_>, form: &Self::Form) -> LemmyResult<UpleteCount> {
|
||||
let conn = &mut get_conn(pool).await?;
|
||||
|
||||
uplete::new(
|
||||
uplete(
|
||||
post_actions::table
|
||||
.filter(post_actions::post_id.eq(form.post_id))
|
||||
.filter(post_actions::person_id.eq(form.person_id)),
|
||||
|
@ -516,10 +516,10 @@ impl ReadComments for PostActions {
|
|||
pool: &mut DbPool<'_>,
|
||||
person_id: PersonId,
|
||||
post_id: Self::IdType,
|
||||
) -> LemmyResult<uplete::Count> {
|
||||
) -> LemmyResult<UpleteCount> {
|
||||
let conn = &mut get_conn(pool).await?;
|
||||
|
||||
uplete::new(
|
||||
uplete(
|
||||
post_actions::table
|
||||
.filter(post_actions::post_id.eq(post_id))
|
||||
.filter(post_actions::person_id.eq(person_id)),
|
||||
|
@ -588,9 +588,10 @@ mod tests {
|
|||
},
|
||||
},
|
||||
traits::{Crud, Likeable, Readable, Saveable},
|
||||
utils::{build_db_pool_for_tests, uplete, RANK_DEFAULT},
|
||||
utils::{build_db_pool_for_tests, RANK_DEFAULT},
|
||||
};
|
||||
use chrono::DateTime;
|
||||
use diesel_uplete::UpleteCount;
|
||||
use lemmy_utils::error::LemmyResult;
|
||||
use pretty_assertions::assert_eq;
|
||||
use serial_test::serial;
|
||||
|
@ -708,17 +709,17 @@ mod tests {
|
|||
assert_eq!(1, scheduled_post_count);
|
||||
|
||||
let like_removed = PostActions::remove_like(pool, inserted_person.id, inserted_post.id).await?;
|
||||
assert_eq!(uplete::Count::only_updated(1), like_removed);
|
||||
assert_eq!(UpleteCount::only_updated(1), like_removed);
|
||||
let saved_removed = PostActions::unsave(pool, &post_saved_form).await?;
|
||||
assert_eq!(uplete::Count::only_updated(1), saved_removed);
|
||||
assert_eq!(UpleteCount::only_updated(1), saved_removed);
|
||||
|
||||
let read_remove_form_1 = PostReadForm::new(inserted_post.id, inserted_person.id);
|
||||
let read_removed_1 = PostActions::mark_as_unread(pool, &read_remove_form_1).await?;
|
||||
assert_eq!(uplete::Count::only_deleted(1), read_removed_1);
|
||||
assert_eq!(UpleteCount::only_deleted(1), read_removed_1);
|
||||
|
||||
let read_remove_form_2 = PostReadForm::new(inserted_post2.id, inserted_person.id);
|
||||
let read_removed_2 = PostActions::mark_as_unread(pool, &read_remove_form_2).await?;
|
||||
assert_eq!(uplete::Count::only_deleted(1), read_removed_2);
|
||||
assert_eq!(UpleteCount::only_deleted(1), read_removed_2);
|
||||
|
||||
let num_deleted = Post::delete(pool, inserted_post.id).await?
|
||||
+ Post::delete(pool, inserted_post2.id).await?
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
use crate::{
|
||||
newtypes::{CommunityId, DbUrl, PaginationCursor, PersonId},
|
||||
utils::{get_conn, uplete, DbPool},
|
||||
utils::{get_conn, DbPool},
|
||||
};
|
||||
use diesel::{
|
||||
associations::HasTable,
|
||||
|
@ -14,6 +14,7 @@ use diesel_async::{
|
|||
AsyncPgConnection,
|
||||
RunQueryDsl,
|
||||
};
|
||||
use diesel_uplete::UpleteCount;
|
||||
use lemmy_utils::{
|
||||
error::{LemmyErrorExt, LemmyErrorType, LemmyResult},
|
||||
settings::structs::Settings,
|
||||
|
@ -106,7 +107,7 @@ pub trait Followable {
|
|||
pool: &mut DbPool<'_>,
|
||||
person_id: PersonId,
|
||||
item_id: Self::IdType,
|
||||
) -> impl Future<Output = LemmyResult<uplete::Count>> + Send
|
||||
) -> impl Future<Output = LemmyResult<UpleteCount>> + Send
|
||||
where
|
||||
Self: Sized;
|
||||
}
|
||||
|
@ -122,7 +123,7 @@ pub trait Joinable {
|
|||
fn leave(
|
||||
pool: &mut DbPool<'_>,
|
||||
form: &Self::Form,
|
||||
) -> impl Future<Output = LemmyResult<uplete::Count>> + Send
|
||||
) -> impl Future<Output = LemmyResult<UpleteCount>> + Send
|
||||
where
|
||||
Self: Sized;
|
||||
}
|
||||
|
@ -140,14 +141,14 @@ pub trait Likeable {
|
|||
pool: &mut DbPool<'_>,
|
||||
person_id: PersonId,
|
||||
item_id: Self::IdType,
|
||||
) -> impl Future<Output = LemmyResult<uplete::Count>> + Send
|
||||
) -> impl Future<Output = LemmyResult<UpleteCount>> + Send
|
||||
where
|
||||
Self: Sized;
|
||||
|
||||
fn remove_all_likes(
|
||||
pool: &mut DbPool<'_>,
|
||||
creator_id: PersonId,
|
||||
) -> impl Future<Output = LemmyResult<uplete::Count>> + Send
|
||||
) -> impl Future<Output = LemmyResult<UpleteCount>> + Send
|
||||
where
|
||||
Self: Sized;
|
||||
|
||||
|
@ -155,7 +156,7 @@ pub trait Likeable {
|
|||
pool: &mut DbPool<'_>,
|
||||
creator_id: PersonId,
|
||||
community_id: CommunityId,
|
||||
) -> impl Future<Output = LemmyResult<uplete::Count>> + Send
|
||||
) -> impl Future<Output = LemmyResult<UpleteCount>> + Send
|
||||
where
|
||||
Self: Sized;
|
||||
}
|
||||
|
@ -171,7 +172,7 @@ pub trait Bannable {
|
|||
fn unban(
|
||||
pool: &mut DbPool<'_>,
|
||||
form: &Self::Form,
|
||||
) -> impl Future<Output = LemmyResult<uplete::Count>> + Send
|
||||
) -> impl Future<Output = LemmyResult<UpleteCount>> + Send
|
||||
where
|
||||
Self: Sized;
|
||||
}
|
||||
|
@ -187,7 +188,7 @@ pub trait Saveable {
|
|||
fn unsave(
|
||||
pool: &mut DbPool<'_>,
|
||||
form: &Self::Form,
|
||||
) -> impl Future<Output = LemmyResult<uplete::Count>> + Send
|
||||
) -> impl Future<Output = LemmyResult<UpleteCount>> + Send
|
||||
where
|
||||
Self: Sized;
|
||||
}
|
||||
|
@ -209,7 +210,7 @@ pub trait Readable {
|
|||
fn mark_as_unread(
|
||||
pool: &mut DbPool<'_>,
|
||||
form: &Self::Form,
|
||||
) -> impl Future<Output = LemmyResult<uplete::Count>> + Send
|
||||
) -> impl Future<Output = LemmyResult<UpleteCount>> + Send
|
||||
where
|
||||
Self: Sized;
|
||||
}
|
||||
|
@ -227,7 +228,7 @@ pub trait ReadComments {
|
|||
pool: &mut DbPool<'_>,
|
||||
person_id: PersonId,
|
||||
item_id: Self::IdType,
|
||||
) -> impl Future<Output = LemmyResult<uplete::Count>> + Send
|
||||
) -> impl Future<Output = LemmyResult<UpleteCount>> + Send
|
||||
where
|
||||
Self: Sized;
|
||||
}
|
||||
|
@ -243,7 +244,7 @@ pub trait Hideable {
|
|||
fn unhide(
|
||||
pool: &mut DbPool<'_>,
|
||||
form: &Self::Form,
|
||||
) -> impl Future<Output = LemmyResult<uplete::Count>> + Send
|
||||
) -> impl Future<Output = LemmyResult<UpleteCount>> + Send
|
||||
where
|
||||
Self: Sized;
|
||||
}
|
||||
|
@ -261,7 +262,7 @@ pub trait Blockable {
|
|||
fn unblock(
|
||||
pool: &mut DbPool<'_>,
|
||||
form: &Self::Form,
|
||||
) -> impl Future<Output = LemmyResult<uplete::Count>> + Send
|
||||
) -> impl Future<Output = LemmyResult<UpleteCount>> + Send
|
||||
where
|
||||
Self: Sized;
|
||||
fn read_block(
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
pub mod queries;
|
||||
pub mod uplete;
|
||||
|
||||
use crate::newtypes::DbUrl;
|
||||
use chrono::TimeDelta;
|
||||
|
|
|
@ -1,426 +0,0 @@
|
|||
use diesel::{
|
||||
associations::HasTable,
|
||||
dsl,
|
||||
expression::{is_aggregate, ValidGrouping},
|
||||
pg::Pg,
|
||||
query_builder::{AsQuery, AstPass, Query, QueryFragment, QueryId},
|
||||
query_dsl::methods::{FilterDsl, SelectDsl},
|
||||
result::Error,
|
||||
sql_types,
|
||||
Column,
|
||||
Expression,
|
||||
Table,
|
||||
};
|
||||
use std::any::TypeId;
|
||||
use tuplex::IntoArray;
|
||||
|
||||
/// Set columns (each specified with `UpleteBuilder::set_null`) to null in the rows found by
|
||||
/// `query`, and delete rows that have no remaining non-null values outside of the primary key
|
||||
pub fn new<Q>(query: Q) -> UpleteBuilder<dsl::Select<Q::Query, <Q::Table as Table>::PrimaryKey>>
|
||||
where
|
||||
Q: AsQuery + HasTable,
|
||||
Q::Table: Default,
|
||||
Q::Query: SelectDsl<<Q::Table as Table>::PrimaryKey>,
|
||||
|
||||
// For better error messages
|
||||
UpleteBuilder<Q>: AsQuery,
|
||||
{
|
||||
UpleteBuilder {
|
||||
query: query.as_query().select(Q::Table::default().primary_key()),
|
||||
set_null_columns: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub struct UpleteBuilder<Q> {
|
||||
query: Q,
|
||||
set_null_columns: Vec<DynColumn>,
|
||||
}
|
||||
|
||||
impl<Q: HasTable> UpleteBuilder<Q> {
|
||||
pub fn set_null<C: Column<Table = Q::Table> + Into<DynColumn>>(mut self, column: C) -> Self {
|
||||
self.set_null_columns.push(column.into());
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl<Q> AsQuery for UpleteBuilder<Q>
|
||||
where
|
||||
Q: HasTable,
|
||||
Q::Table: Default + QueryFragment<Pg> + Send + 'static,
|
||||
<Q::Table as Table>::PrimaryKey: IntoArray<DynColumn> + QueryFragment<Pg> + Send + 'static,
|
||||
<Q::Table as Table>::AllColumns: IntoArray<DynColumn>,
|
||||
<<Q::Table as Table>::PrimaryKey as IntoArray<DynColumn>>::Output: IntoIterator<Item = DynColumn>,
|
||||
<<Q::Table as Table>::AllColumns as IntoArray<DynColumn>>::Output: IntoIterator<Item = DynColumn>,
|
||||
Q: Clone + FilterDsl<AllNull> + FilterDsl<dsl::not<AllNull>>,
|
||||
dsl::Filter<Q, AllNull>: QueryFragment<Pg> + Send + 'static,
|
||||
dsl::Filter<Q, dsl::not<AllNull>>: QueryFragment<Pg> + Send + 'static,
|
||||
{
|
||||
type Query = UpleteQuery;
|
||||
|
||||
type SqlType = (sql_types::BigInt, sql_types::BigInt);
|
||||
|
||||
fn as_query(self) -> Self::Query {
|
||||
let table = Q::Table::default;
|
||||
let deletion_condition = AllNull(
|
||||
Q::Table::all_columns()
|
||||
.into_array()
|
||||
.into_iter()
|
||||
.filter(|c: &DynColumn| {
|
||||
table()
|
||||
.primary_key()
|
||||
.into_array()
|
||||
.into_iter()
|
||||
.chain(self.set_null_columns.iter().cloned())
|
||||
.all(|excluded_column| excluded_column.type_id != c.type_id)
|
||||
})
|
||||
.collect::<Vec<_>>(),
|
||||
);
|
||||
UpleteQuery {
|
||||
// Updated rows and deleted rows must not overlap, so updating all rows and using the returned
|
||||
// new rows to determine which ones to delete is not an option.
|
||||
//
|
||||
// https://www.postgresql.org/docs/16/queries-with.html#QUERIES-WITH-MODIFYING
|
||||
//
|
||||
// "Trying to update the same row twice in a single statement is not supported. Only one of
|
||||
// the modifications takes place, but it is not easy (and sometimes not possible) to reliably
|
||||
// predict which one. This also applies to deleting a row that was already updated in the same
|
||||
// statement: only the update is performed."
|
||||
update_subquery: Box::new(
|
||||
self
|
||||
.query
|
||||
.clone()
|
||||
.filter(dsl::not(deletion_condition.clone())),
|
||||
),
|
||||
delete_subquery: Box::new(self.query.filter(deletion_condition)),
|
||||
table: Box::new(table()),
|
||||
primary_key: Box::new(table().primary_key()),
|
||||
set_null_columns: self.set_null_columns,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct UpleteQuery {
|
||||
update_subquery: Box<dyn QueryFragment<Pg> + Send + 'static>,
|
||||
delete_subquery: Box<dyn QueryFragment<Pg> + Send + 'static>,
|
||||
table: Box<dyn QueryFragment<Pg> + Send + 'static>,
|
||||
primary_key: Box<dyn QueryFragment<Pg> + Send + 'static>,
|
||||
set_null_columns: Vec<DynColumn>,
|
||||
}
|
||||
|
||||
impl QueryId for UpleteQuery {
|
||||
type QueryId = ();
|
||||
|
||||
const HAS_STATIC_QUERY_ID: bool = false;
|
||||
}
|
||||
|
||||
impl Query for UpleteQuery {
|
||||
type SqlType = (sql_types::BigInt, sql_types::BigInt);
|
||||
}
|
||||
|
||||
impl QueryFragment<Pg> for UpleteQuery {
|
||||
fn walk_ast<'b>(&'b self, mut out: AstPass<'_, 'b, Pg>) -> Result<(), Error> {
|
||||
assert_ne!(self.set_null_columns.len(), 0, "`set_null` was not called");
|
||||
|
||||
// This is checked by require_uplete triggers
|
||||
out.push_sql("/**/");
|
||||
|
||||
// Declare `update_keys` and `delete_keys` CTEs, which select primary keys
|
||||
for (prefix, subquery) in [
|
||||
("WITH update_keys", &self.update_subquery),
|
||||
(", delete_keys", &self.delete_subquery),
|
||||
] {
|
||||
out.push_sql(prefix);
|
||||
out.push_sql(" AS (");
|
||||
subquery.walk_ast(out.reborrow())?;
|
||||
out.push_sql(" FOR UPDATE)");
|
||||
}
|
||||
|
||||
// Update rows that are referenced in `update_keys`
|
||||
out.push_sql(", update_result AS (UPDATE ");
|
||||
self.table.walk_ast(out.reborrow())?;
|
||||
let mut item_prefix = " SET ";
|
||||
for column in &self.set_null_columns {
|
||||
out.push_sql(item_prefix);
|
||||
out.push_identifier(column.name)?;
|
||||
out.push_sql(" = NULL");
|
||||
item_prefix = ",";
|
||||
}
|
||||
out.push_sql(" WHERE (");
|
||||
self.primary_key.walk_ast(out.reborrow())?;
|
||||
out.push_sql(") = ANY (SELECT * FROM update_keys) RETURNING 1)");
|
||||
|
||||
// Delete rows that are referenced in `delete_keys`
|
||||
out.push_sql(", delete_result AS (DELETE FROM ");
|
||||
self.table.walk_ast(out.reborrow())?;
|
||||
out.push_sql(" WHERE (");
|
||||
self.primary_key.walk_ast(out.reborrow())?;
|
||||
out.push_sql(") = ANY (SELECT * FROM delete_keys) RETURNING 1)");
|
||||
|
||||
// Count updated rows and deleted rows (`RETURNING 1` makes this possible)
|
||||
out.push_sql(" SELECT (SELECT count(*) FROM update_result)");
|
||||
out.push_sql(", (SELECT count(*) FROM delete_result)");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
// Types other than `DynColumn` are only used in tests
|
||||
#[derive(Clone)]
|
||||
pub struct AllNull<T = DynColumn>(Vec<T>);
|
||||
|
||||
impl<T> Expression for AllNull<T> {
|
||||
type SqlType = sql_types::Bool;
|
||||
}
|
||||
|
||||
impl<T> ValidGrouping<()> for AllNull<T> {
|
||||
type IsAggregate = is_aggregate::No;
|
||||
}
|
||||
|
||||
impl<T: QueryFragment<Pg>> QueryFragment<Pg> for AllNull<T> {
|
||||
fn walk_ast<'b>(&'b self, mut out: AstPass<'_, 'b, Pg>) -> Result<(), Error> {
|
||||
// Must produce a valid expression even if `self.0` is empty
|
||||
out.push_sql("(TRUE");
|
||||
for item in &self.0 {
|
||||
out.push_sql(" AND (");
|
||||
item.walk_ast(out.reborrow())?;
|
||||
out.push_sql(" IS NULL)");
|
||||
}
|
||||
out.push_sql(")");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct DynColumn {
|
||||
type_id: TypeId,
|
||||
name: &'static str,
|
||||
}
|
||||
|
||||
impl<T: Column + 'static> From<T> for DynColumn {
|
||||
fn from(_value: T) -> Self {
|
||||
DynColumn {
|
||||
type_id: TypeId::of::<T>(),
|
||||
name: T::NAME,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl QueryFragment<Pg> for DynColumn {
|
||||
fn walk_ast<'b>(&'b self, mut out: AstPass<'_, 'b, Pg>) -> Result<(), Error> {
|
||||
out.push_identifier(self.name)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Queryable, PartialEq, Eq, Debug)]
|
||||
pub struct Count {
|
||||
pub updated: i64,
|
||||
pub deleted: i64,
|
||||
}
|
||||
|
||||
impl Count {
|
||||
pub fn only_updated(n: i64) -> Self {
|
||||
Count {
|
||||
updated: n,
|
||||
deleted: 0,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn only_deleted(n: i64) -> Self {
|
||||
Count {
|
||||
updated: 0,
|
||||
deleted: n,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::AllNull;
|
||||
use crate::utils::{build_db_pool_for_tests, get_conn, DbConn};
|
||||
use diesel::{
|
||||
debug_query,
|
||||
insert_into,
|
||||
pg::Pg,
|
||||
query_builder::{AsQuery, QueryId},
|
||||
select,
|
||||
sql_types,
|
||||
AppearsOnTable,
|
||||
ExpressionMethods,
|
||||
IntoSql,
|
||||
QueryDsl,
|
||||
SelectableExpression,
|
||||
};
|
||||
use diesel_async::{RunQueryDsl, SimpleAsyncConnection};
|
||||
use lemmy_utils::error::LemmyResult;
|
||||
use pretty_assertions::assert_eq;
|
||||
use serial_test::serial;
|
||||
|
||||
impl<T, QS> AppearsOnTable<QS> for AllNull<T> {}
|
||||
|
||||
impl<T, QS> SelectableExpression<QS> for AllNull<T> {}
|
||||
|
||||
impl<T> QueryId for AllNull<T> {
|
||||
type QueryId = ();
|
||||
const HAS_STATIC_QUERY_ID: bool = false;
|
||||
}
|
||||
|
||||
diesel::table! {
|
||||
t (id1, id2) {
|
||||
// uplete doesn't work for non-tuple primary key
|
||||
id1 -> Int4,
|
||||
id2 -> Int4,
|
||||
a -> Nullable<Int4>,
|
||||
b -> Nullable<Int4>,
|
||||
}
|
||||
}
|
||||
|
||||
async fn expect_rows(
|
||||
conn: &mut DbConn<'_>,
|
||||
expected: &[(Option<i32>, Option<i32>)],
|
||||
) -> LemmyResult<()> {
|
||||
let rows: Vec<(Option<i32>, Option<i32>)> = t::table
|
||||
.select((t::a, t::b))
|
||||
.order_by(t::id1)
|
||||
.load(conn)
|
||||
.await?;
|
||||
assert_eq!(expected, &rows);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Main purpose of this test is to check accuracy of the returned `Count`, which other modules'
|
||||
// tests rely on
|
||||
#[tokio::test]
|
||||
#[serial]
|
||||
async fn test_count() -> LemmyResult<()> {
|
||||
let pool = &build_db_pool_for_tests();
|
||||
let pool = &mut pool.into();
|
||||
let mut conn = get_conn(pool).await?;
|
||||
|
||||
conn
|
||||
.batch_execute("CREATE TABLE t (id1 serial, id2 int NOT NULL DEFAULT 1, a int, b int, PRIMARY KEY (id1, id2));")
|
||||
.await?;
|
||||
expect_rows(&mut conn, &[]).await?;
|
||||
|
||||
insert_into(t::table)
|
||||
.values(&[
|
||||
(t::a.eq(Some(1)), t::b.eq(Some(2))),
|
||||
(t::a.eq(Some(3)), t::b.eq(None)),
|
||||
(t::a.eq(Some(4)), t::b.eq(Some(5))),
|
||||
])
|
||||
.execute(&mut conn)
|
||||
.await?;
|
||||
expect_rows(
|
||||
&mut conn,
|
||||
&[(Some(1), Some(2)), (Some(3), None), (Some(4), Some(5))],
|
||||
)
|
||||
.await?;
|
||||
|
||||
let count1 = super::new(t::table)
|
||||
.set_null(t::a)
|
||||
.get_result(&mut conn)
|
||||
.await?;
|
||||
assert_eq!(
|
||||
super::Count {
|
||||
updated: 2,
|
||||
deleted: 1
|
||||
},
|
||||
count1
|
||||
);
|
||||
expect_rows(&mut conn, &[(None, Some(2)), (None, Some(5))]).await?;
|
||||
|
||||
let count2 = super::new(t::table)
|
||||
.set_null(t::b)
|
||||
.get_result(&mut conn)
|
||||
.await?;
|
||||
assert_eq!(super::Count::only_deleted(2), count2);
|
||||
expect_rows(&mut conn, &[]).await?;
|
||||
|
||||
conn.batch_execute("DROP TABLE t;").await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn expected_sql(check_null: &str, set_null: &str) -> String {
|
||||
let with_queries = {
|
||||
let key = r#""t"."id1", "t"."id2""#;
|
||||
let t = r#""t""#;
|
||||
|
||||
let update_keys = format!("SELECT {key} FROM {t} WHERE NOT (({check_null})) FOR UPDATE");
|
||||
let delete_keys = format!("SELECT {key} FROM {t} WHERE ({check_null}) FOR UPDATE");
|
||||
let update_result = format!(
|
||||
"UPDATE {t} SET {set_null} WHERE ({key}) = ANY (SELECT * FROM update_keys) RETURNING 1"
|
||||
);
|
||||
let delete_result =
|
||||
format!("DELETE FROM {t} WHERE ({key}) = ANY (SELECT * FROM delete_keys) RETURNING 1");
|
||||
|
||||
format!("update_keys AS ({update_keys}), delete_keys AS ({delete_keys}), update_result AS ({update_result}), delete_result AS ({delete_result})")
|
||||
};
|
||||
let update_count = "SELECT count(*) FROM update_result";
|
||||
let delete_count = "SELECT count(*) FROM delete_result";
|
||||
|
||||
format!(r#"/**/WITH {with_queries} SELECT ({update_count}), ({delete_count}) -- binds: []"#)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_generated_sql() {
|
||||
// Unlike the `get_result` method, `debug_query` does not automatically call `as_query`
|
||||
assert_eq!(
|
||||
debug_query::<Pg, _>(&super::new(t::table).set_null(t::b).as_query()).to_string(),
|
||||
expected_sql(r#"TRUE AND ("a" IS NULL)"#, r#""b" = NULL"#)
|
||||
);
|
||||
assert_eq!(
|
||||
debug_query::<Pg, _>(
|
||||
&super::new(t::table)
|
||||
.set_null(t::a)
|
||||
.set_null(t::b)
|
||||
.as_query()
|
||||
)
|
||||
.to_string(),
|
||||
expected_sql(r#"TRUE"#, r#""a" = NULL,"b" = NULL"#)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_count_methods() {
|
||||
assert_eq!(
|
||||
super::Count::only_updated(1),
|
||||
super::Count {
|
||||
updated: 1,
|
||||
deleted: 0
|
||||
}
|
||||
);
|
||||
assert_eq!(
|
||||
super::Count::only_deleted(1),
|
||||
super::Count {
|
||||
updated: 0,
|
||||
deleted: 1
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[serial]
|
||||
async fn test_all_null() -> LemmyResult<()> {
|
||||
let pool = &build_db_pool_for_tests();
|
||||
let pool = &mut pool.into();
|
||||
let mut conn = get_conn(pool).await?;
|
||||
|
||||
let some = Some(1).into_sql::<sql_types::Nullable<sql_types::Integer>>();
|
||||
let none = None::<i32>.into_sql::<sql_types::Nullable<sql_types::Integer>>();
|
||||
|
||||
// Allows type inference for `vec![]`
|
||||
let mut all_null = |items| select(AllNull(items)).get_result::<bool>(&mut conn);
|
||||
|
||||
assert!(all_null(vec![]).await?);
|
||||
assert!(all_null(vec![none]).await?);
|
||||
assert!(all_null(vec![none, none]).await?);
|
||||
assert!(all_null(vec![none, none, none]).await?);
|
||||
assert!(!all_null(vec![some]).await?);
|
||||
assert!(!all_null(vec![some, none]).await?);
|
||||
assert!(!all_null(vec![none, some, none]).await?);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
|
@ -18,7 +18,7 @@ doctest = false
|
|||
workspace = true
|
||||
|
||||
[features]
|
||||
full = ["diesel", "diesel_ltree", "diesel-derive-enum"]
|
||||
full = ["diesel", "diesel_ltree", "diesel-derive-enum", "diesel-uplete"]
|
||||
ts-rs = ["dep:ts-rs"]
|
||||
|
||||
[dependencies]
|
||||
|
@ -28,3 +28,4 @@ diesel = { workspace = true, optional = true }
|
|||
diesel_ltree = { workspace = true, optional = true }
|
||||
ts-rs = { workspace = true, optional = true }
|
||||
diesel-derive-enum = { workspace = true, optional = true }
|
||||
diesel-uplete = { workspace = true, optional = true }
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
pub mod enums;
|
||||
#[cfg(feature = "full")]
|
||||
pub mod schema;
|
||||
#[cfg(feature = "full")]
|
||||
pub mod table_impls;
|
||||
|
|
35
crates/db_schema_file/src/table_impls.rs
Normal file
35
crates/db_schema_file/src/table_impls.rs
Normal file
|
@ -0,0 +1,35 @@
|
|||
use crate::schema::{
|
||||
comment_actions,
|
||||
community_actions,
|
||||
instance_actions,
|
||||
person_actions,
|
||||
post_actions,
|
||||
};
|
||||
|
||||
impl diesel_uplete::SupportedTable for comment_actions::table {
|
||||
type Key = (comment_actions::person_id, comment_actions::comment_id);
|
||||
type AdditionalIgnoredColumns = ();
|
||||
}
|
||||
|
||||
impl diesel_uplete::SupportedTable for community_actions::table {
|
||||
type Key = (
|
||||
community_actions::person_id,
|
||||
community_actions::community_id,
|
||||
);
|
||||
type AdditionalIgnoredColumns = ();
|
||||
}
|
||||
|
||||
impl diesel_uplete::SupportedTable for instance_actions::table {
|
||||
type Key = (instance_actions::person_id, instance_actions::instance_id);
|
||||
type AdditionalIgnoredColumns = ();
|
||||
}
|
||||
|
||||
impl diesel_uplete::SupportedTable for person_actions::table {
|
||||
type Key = (person_actions::person_id, person_actions::target_id);
|
||||
type AdditionalIgnoredColumns = ();
|
||||
}
|
||||
|
||||
impl diesel_uplete::SupportedTable for post_actions::table {
|
||||
type Key = (post_actions::person_id, post_actions::post_id);
|
||||
type AdditionalIgnoredColumns = ();
|
||||
}
|
|
@ -56,3 +56,4 @@ tokio = { workspace = true }
|
|||
pretty_assertions = { workspace = true }
|
||||
url = { workspace = true }
|
||||
test-context = "0.4.1"
|
||||
diesel-uplete.workspace = true
|
||||
|
|
|
@ -566,6 +566,7 @@ mod tests {
|
|||
};
|
||||
use chrono::Utc;
|
||||
use diesel_async::SimpleAsyncConnection;
|
||||
use diesel_uplete::UpleteCount;
|
||||
use lemmy_db_schema::{
|
||||
impls::actor_language::UNDETERMINED_ID,
|
||||
newtypes::LanguageId,
|
||||
|
@ -604,7 +605,7 @@ mod tests {
|
|||
},
|
||||
test_data::TestData,
|
||||
traits::{Bannable, Blockable, Crud, Followable, Hideable, Joinable, Likeable, Readable},
|
||||
utils::{build_db_pool, get_conn, uplete, ActualDbPool, DbPool},
|
||||
utils::{build_db_pool, get_conn, ActualDbPool, DbPool},
|
||||
};
|
||||
use lemmy_db_schema_file::enums::{CommunityFollowerState, CommunityVisibility, ListingType};
|
||||
use lemmy_db_views_local_user::LocalUserView;
|
||||
|
@ -1012,7 +1013,7 @@ mod tests {
|
|||
);
|
||||
|
||||
let like_removed = PostActions::remove_like(pool, data.tegan.person.id, data.post.id).await?;
|
||||
assert_eq!(uplete::Count::only_deleted(1), like_removed);
|
||||
assert_eq!(UpleteCount::only_deleted(1), like_removed);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
@ -1058,7 +1059,7 @@ mod tests {
|
|||
)
|
||||
.await?;
|
||||
|
||||
assert_eq!(uplete::Count::only_deleted(1), note_removed);
|
||||
assert_eq!(UpleteCount::only_deleted(1), note_removed);
|
||||
assert!(post_listing.person_actions.is_none());
|
||||
|
||||
Ok(())
|
||||
|
@ -1151,7 +1152,7 @@ mod tests {
|
|||
// Remove the like
|
||||
let like_removed =
|
||||
PostActions::remove_like(pool, data.tegan.person.id, data.bot_post.id).await?;
|
||||
assert_eq!(uplete::Count::only_deleted(1), like_removed);
|
||||
assert_eq!(UpleteCount::only_deleted(1), like_removed);
|
||||
|
||||
let person_like_removed =
|
||||
PersonActions::remove_like(pool, data.tegan.person.id, data.bot.person.id, 1).await?;
|
||||
|
@ -1211,7 +1212,7 @@ mod tests {
|
|||
|
||||
let like_removed =
|
||||
PostActions::remove_like(pool, data.tegan.person.id, data.bot_post.id).await?;
|
||||
assert_eq!(uplete::Count::only_deleted(1), like_removed);
|
||||
assert_eq!(UpleteCount::only_deleted(1), like_removed);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
@ -57,6 +57,7 @@ actix-web-prom = "0.10.0"
|
|||
actix-cors = "0.7.1"
|
||||
rand = "0.9.1"
|
||||
percent-encoding = "2.3.1"
|
||||
diesel-uplete.workspace = true
|
||||
|
||||
[dev-dependencies]
|
||||
pretty_assertions.workspace = true
|
||||
|
|
|
@ -14,6 +14,7 @@ use diesel::{
|
|||
QueryableByName,
|
||||
};
|
||||
use diesel_async::{AsyncPgConnection, RunQueryDsl};
|
||||
use diesel_uplete::uplete;
|
||||
use lemmy_api_utils::{
|
||||
context::LemmyContext,
|
||||
send_activity::{ActivityChannel, SendActivityData},
|
||||
|
@ -27,7 +28,7 @@ use lemmy_db_schema::{
|
|||
post::{Post, PostUpdateForm},
|
||||
},
|
||||
traits::Crud,
|
||||
utils::{functions::coalesce, get_conn, now, uplete, DbPool, DELETED_REPLACEMENT_TEXT},
|
||||
utils::{functions::coalesce, get_conn, now, DbPool, DELETED_REPLACEMENT_TEXT},
|
||||
};
|
||||
use lemmy_db_schema_file::schema::{
|
||||
captcha_answer,
|
||||
|
@ -394,23 +395,19 @@ async fn update_banned_when_expired(pool: &mut DbPool<'_>) -> LemmyResult<()> {
|
|||
info!("Updating banned column if it expires ...");
|
||||
let mut conn = get_conn(pool).await?;
|
||||
|
||||
uplete::new(
|
||||
community_actions::table.filter(community_actions::ban_expires_at.lt(now().nullable())),
|
||||
)
|
||||
.set_null(community_actions::received_ban_at)
|
||||
.set_null(community_actions::ban_expires_at)
|
||||
.as_query()
|
||||
.execute(&mut conn)
|
||||
.await?;
|
||||
uplete(community_actions::table.filter(community_actions::ban_expires_at.lt(now().nullable())))
|
||||
.set_null(community_actions::received_ban_at)
|
||||
.set_null(community_actions::ban_expires_at)
|
||||
.as_query()
|
||||
.execute(&mut conn)
|
||||
.await?;
|
||||
|
||||
uplete::new(
|
||||
instance_actions::table.filter(instance_actions::ban_expires_at.lt(now().nullable())),
|
||||
)
|
||||
.set_null(instance_actions::received_ban_at)
|
||||
.set_null(instance_actions::ban_expires_at)
|
||||
.as_query()
|
||||
.execute(&mut conn)
|
||||
.await?;
|
||||
uplete(instance_actions::table.filter(instance_actions::ban_expires_at.lt(now().nullable())))
|
||||
.set_null(instance_actions::received_ban_at)
|
||||
.set_null(instance_actions::ban_expires_at)
|
||||
.as_query()
|
||||
.execute(&mut conn)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue