Remove usages of FIND_IN_SET

FIND_IN_SET doesn't make use of indexes
This commit is contained in:
jude 2024-10-10 19:06:56 +01:00
parent 137ae6f24b
commit 6f223b1bc2
7 changed files with 163 additions and 156 deletions

View File

@ -31,31 +31,33 @@ impl Recordable for Options {
.filter(|(_, channel)| channel.is_text_based()) .filter(|(_, channel)| channel.is_text_based())
.map(|(id, _)| id.get().to_string()) .map(|(id, _)| id.get().to_string())
.collect::<Vec<String>>() .collect::<Vec<String>>()
.join(",")
}) { }) {
sqlx::query!( let placeholder = vec!["?"; channels.len()].join(",");
let sql = format!(
" "
UPDATE reminders UPDATE reminders
INNER JOIN `channels` INNER JOIN `channels`
ON `channels`.id = reminders.channel_id ON `channels`.id = reminders.channel_id
SET reminders.`utc_time` = DATE_ADD(reminders.`utc_time`, INTERVAL ? SECOND) SET reminders.`utc_time` = DATE_ADD(reminders.`utc_time`, INTERVAL ? SECOND)
WHERE FIND_IN_SET(channels.`channel`, ?) WHERE channels.`channel` IN ({placeholder})
", "
combined_time as i64, );
channels
) let mut query = sqlx::query(&sql).bind(combined_time);
.execute(&ctx.data().database) for channel in channels {
.await query = query.bind(channel);
.unwrap(); }
query.execute(&ctx.data().database).await.unwrap();
} else { } else {
sqlx::query!( sqlx::query!(
" "
UPDATE reminders UPDATE reminders
INNER JOIN `channels` INNER JOIN `channels`
ON `channels`.id = reminders.channel_id ON `channels`.id = reminders.channel_id
SET reminders.`utc_time` = reminders.`utc_time` + ? SET reminders.`utc_time` = reminders.`utc_time` + ?
WHERE channels.`channel` = ? WHERE channels.`channel` = ?
", ",
combined_time as i64, combined_time as i64,
ctx.channel_id().get() ctx.channel_id().get()
) )

View File

@ -153,12 +153,12 @@ impl ComponentDataModel {
ComponentDataModel::DelSelector(selector) => { ComponentDataModel::DelSelector(selector) => {
if let ComponentInteractionDataKind::StringSelect { values } = &component.data.kind if let ComponentInteractionDataKind::StringSelect { values } = &component.data.kind
{ {
let placeholder = values.iter().map(|_| "?").collect::<Vec<&str>>().join(","); let placeholder = vec!["?"; values.len()].join(",");
let sql = format!( let sql = format!(
"UPDATE reminders SET `status` = 'deleted' WHERE id IN ({placeholder})" "UPDATE reminders SET `status` = 'deleted' WHERE id IN ({placeholder})"
); );
let mut query = sqlx::query(&sql);
let mut query = sqlx::query(&sql);
for id in values { for id in values {
query = query.bind(id); query = query.bind(id);
} }
@ -269,17 +269,15 @@ impl ComponentDataModel {
if let ComponentInteractionDataKind::StringSelect { values } = if let ComponentInteractionDataKind::StringSelect { values } =
&component.data.kind &component.data.kind
{ {
let selected_id = values.join(","); let placeholder = vec!["?"; values.len()].join(",");
let sql = format!("DELETE FROM todos WHERE id IN ({placeholder})");
sqlx::query!( let mut query = sqlx::query(&sql);
" for id in values {
DELETE FROM todos WHERE FIND_IN_SET(id, ?) query = query.bind(id);
", }
selected_id
) query.execute(&data.database).await.unwrap();
.execute(&data.database)
.await
.unwrap();
let values = if let Some(uid) = selector.user_id { let values = if let Some(uid) = selector.user_id {
sqlx::query!( sqlx::query!(

View File

@ -8,17 +8,6 @@ use std::{
hash::{Hash, Hasher}, hash::{Hash, Hasher},
}; };
use chrono::{DateTime, NaiveDateTime, Utc};
use chrono_tz::Tz;
use poise::{
serenity_prelude::{
model::id::{ChannelId, GuildId, UserId},
ButtonStyle, Cache, ChannelType, CreateActionRow, CreateButton, CreateEmbed, ReactionType,
},
CreateReply,
};
use sqlx::Executor;
use crate::{ use crate::{
commands::look::{LookFlags, TimeDisplayType}, commands::look::{LookFlags, TimeDisplayType},
component_models::{ComponentDataModel, UndoReminder}, component_models::{ComponentDataModel, UndoReminder},
@ -36,8 +25,18 @@ use crate::{
utils::{check_guild_subscription, check_subscription}, utils::{check_guild_subscription, check_subscription},
Context, Database, Error, Context, Database, Error,
}; };
use chrono::{DateTime, NaiveDateTime, Utc};
use chrono_tz::Tz;
use poise::{
serenity_prelude::{
model::id::{ChannelId, GuildId, UserId},
ButtonStyle, Cache, ChannelType, CreateActionRow, CreateButton, CreateEmbed, ReactionType,
},
CreateReply,
};
use sqlx::{Executor, FromRow};
#[derive(Debug, Clone)] #[derive(Debug, Clone, FromRow)]
#[allow(dead_code)] #[allow(dead_code)]
pub struct Reminder { pub struct Reminder {
pub id: u32, pub id: u32,
@ -140,7 +139,7 @@ impl Reminder {
channel_id: C, channel_id: C,
flags: &LookFlags, flags: &LookFlags,
) -> Vec<Self> { ) -> Vec<Self> {
let enabled = if flags.show_disabled { "0,1" } else { "1" }; let enabled = if flags.show_disabled { "0" } else { "1" };
let channel_id = channel_id.into(); let channel_id = channel_id.into();
sqlx::query_as_unchecked!( sqlx::query_as_unchecked!(
@ -168,7 +167,7 @@ impl Reminder {
WHERE WHERE
`status` = 'pending' AND `status` = 'pending' AND
channels.channel = ? AND channels.channel = ? AND
FIND_IN_SET(reminders.enabled, ?) reminders.enabled >= ?
ORDER BY ORDER BY
reminders.utc_time reminders.utc_time
", ",
@ -194,17 +193,16 @@ impl Reminder {
.keys() .keys()
.into_iter() .into_iter()
.map(|k| k.get().to_string()) .map(|k| k.get().to_string())
.collect::<Vec<String>>() .collect::<Vec<String>>(),
.join(","),
) )
} else { } else {
None None
}; };
match channel_query { match channel_query {
Some(channel_query) => { Some(channels) => {
sqlx::query_as_unchecked!( let placeholder = vec!["?"; channels.len()].join(",");
Self, let sql = format!(
" "
SELECT SELECT
reminders.id, reminders.id,
@ -227,12 +225,16 @@ impl Reminder {
channels.id = reminders.channel_id channels.id = reminders.channel_id
WHERE WHERE
`status` = 'pending' AND `status` = 'pending' AND
FIND_IN_SET(channels.channel, ?) channels.channel IN ({placeholder})
", "
channel_query );
)
.fetch_all(pool) let mut query = sqlx::query_as::<Database, Self>(&sql);
.await for channel in channels {
query = query.bind(channel);
}
query.fetch_all(pool).await
} }
None => { None => {
sqlx::query_as_unchecked!( sqlx::query_as_unchecked!(

View File

@ -72,55 +72,60 @@ pub async fn get_reminders(
match channels_res { match channels_res {
Ok(channels) => { Ok(channels) => {
let channels = channels let channels =
.keys() channels.keys().into_iter().map(|k| k.get().to_string()).collect::<Vec<String>>();
.into_iter()
.map(|k| k.get().to_string())
.collect::<Vec<String>>()
.join(",");
sqlx::query_as_unchecked!( let placeholder = vec!["?"; channels.len()].join(",");
GetReminder, let sql = format!(
" "
SELECT SELECT
reminders.attachment_name, reminders.attachment_name,
reminders.avatar, reminders.avatar,
channels.channel, channels.channel,
reminders.content, reminders.content,
reminders.embed_author, reminders.embed_author,
reminders.embed_author_url, reminders.embed_author_url,
reminders.embed_color, reminders.embed_color,
reminders.embed_description, reminders.embed_description,
reminders.embed_footer, reminders.embed_footer,
reminders.embed_footer_url, reminders.embed_footer_url,
reminders.embed_image_url, reminders.embed_image_url,
reminders.embed_thumbnail_url, reminders.embed_thumbnail_url,
reminders.embed_title, reminders.embed_title,
IFNULL(reminders.embed_fields, '[]') AS embed_fields, IFNULL(reminders.embed_fields, '[]') AS embed_fields,
reminders.enabled, reminders.enabled,
reminders.expires, reminders.expires,
reminders.interval_seconds, reminders.interval_seconds,
reminders.interval_days, reminders.interval_days,
reminders.interval_months, reminders.interval_months,
reminders.name, reminders.name,
reminders.restartable, reminders.restartable,
reminders.tts, reminders.tts,
reminders.uid, reminders.uid,
reminders.username, reminders.username,
reminders.utc_time reminders.utc_time
FROM reminders FROM reminders
INNER JOIN channels ON channels.id = reminders.channel_id INNER JOIN channels
WHERE `status` = 'pending' AND FIND_IN_SET(channels.channel, ?)", ON channels.id = reminders.channel_id
channels WHERE `status` = 'pending'
) AND channels.channel IN ({placeholder})
.fetch_all(pool.inner()) "
.await );
.map(|r| Ok(json!(r)))
.unwrap_or_else(|e| {
warn!("Failed to complete SQL query: {:?}", e);
json_err!("Could not load reminders") let mut query = sqlx::query_as::<Database, GetReminder>(&sql);
}) for channel in channels {
query = query.bind(channel);
}
query
.fetch_all(pool.inner())
.await
.map(|reminders| Ok(json!(reminders)))
.unwrap_or_else(|e| {
warn!("Failed to complete SQL query: {:?}", e);
json_err!("Could not load reminders")
})
} }
Err(e) => { Err(e) => {
warn!("Could not fetch channels from {}: {:?}", id, e); warn!("Could not fetch channels from {}: {:?}", id, e);

View File

@ -1,3 +1,14 @@
use crate::web::{
check_authorization,
guards::transaction::Transaction,
routes::{
dashboard::{
create_reminder, CreateReminder, ImportBody, ReminderCsv, ReminderTemplateCsv, TodoCsv,
},
JsonResult,
},
};
use crate::Database;
use base64::{prelude::BASE64_STANDARD, Engine}; use base64::{prelude::BASE64_STANDARD, Engine};
use csv::{QuoteStyle, WriterBuilder}; use csv::{QuoteStyle, WriterBuilder};
use log::warn; use log::warn;
@ -14,17 +25,6 @@ use serenity::{
}; };
use sqlx::{MySql, Pool}; use sqlx::{MySql, Pool};
use crate::web::{
check_authorization,
guards::transaction::Transaction,
routes::{
dashboard::{
create_reminder, CreateReminder, ImportBody, ReminderCsv, ReminderTemplateCsv, TodoCsv,
},
JsonResult,
},
};
#[get("/api/guild/<id>/export/reminders")] #[get("/api/guild/<id>/export/reminders")]
pub async fn export_reminders( pub async fn export_reminders(
id: u64, id: u64,
@ -40,48 +40,47 @@ pub async fn export_reminders(
match channels_res { match channels_res {
Ok(channels) => { Ok(channels) => {
let channels = channels let channels =
.keys() channels.keys().into_iter().map(|k| k.get().to_string()).collect::<Vec<String>>();
.into_iter() let placeholder = vec!["?"; channels.len()].join(",");
.map(|k| k.get().to_string()) let sql = format!(
.collect::<Vec<String>>() "
.join(","); SELECT
reminders.attachment,
let result = sqlx::query_as_unchecked!( reminders.attachment_name,
ReminderCsv, reminders.avatar,
"SELECT CONCAT('#', channels.channel) AS channel,
reminders.attachment, reminders.content,
reminders.attachment_name, reminders.embed_author,
reminders.avatar, reminders.embed_author_url,
CONCAT('#', channels.channel) AS channel, reminders.embed_color,
reminders.content, reminders.embed_description,
reminders.embed_author, reminders.embed_footer,
reminders.embed_author_url, reminders.embed_footer_url,
reminders.embed_color, reminders.embed_image_url,
reminders.embed_description, reminders.embed_thumbnail_url,
reminders.embed_footer, reminders.embed_title,
reminders.embed_footer_url, reminders.embed_fields,
reminders.embed_image_url, reminders.enabled,
reminders.embed_thumbnail_url, reminders.expires,
reminders.embed_title, reminders.interval_seconds,
reminders.embed_fields, reminders.interval_days,
reminders.enabled, reminders.interval_months,
reminders.expires, reminders.name,
reminders.interval_seconds, reminders.restartable,
reminders.interval_days, reminders.tts,
reminders.interval_months, reminders.username,
reminders.name, reminders.utc_time
reminders.restartable,
reminders.tts,
reminders.username,
reminders.utc_time
FROM reminders FROM reminders
LEFT JOIN channels ON channels.id = reminders.channel_id LEFT JOIN channels
WHERE FIND_IN_SET(channels.channel, ?) AND status = 'pending'", ON channels.id = reminders.channel_id
channels WHERE status = 'pending'
) AND channels.channel IN ({placeholder})
.fetch_all(pool.inner()) "
.await; );
let result =
sqlx::query_as::<Database, ReminderCsv>(&sql).fetch_all(pool.inner()).await;
match result { match result {
Ok(reminders) => { Ok(reminders) => {

View File

@ -17,6 +17,7 @@ use serenity::{
model::id::{ChannelId, GuildId, UserId}, model::id::{ChannelId, GuildId, UserId},
}; };
use sqlx::types::Json; use sqlx::types::Json;
use sqlx::FromRow;
use crate::web::{ use crate::web::{
catchers::internal_server_error, catchers::internal_server_error,
@ -177,7 +178,7 @@ pub struct CreateReminder {
utc_time: NaiveDateTime, utc_time: NaiveDateTime,
} }
#[derive(Serialize)] #[derive(Serialize, FromRow)]
pub struct GetReminder { pub struct GetReminder {
attachment_name: Option<String>, attachment_name: Option<String>,
avatar: Option<String>, avatar: Option<String>,
@ -209,7 +210,7 @@ pub struct GetReminder {
utc_time: NaiveDateTime, utc_time: NaiveDateTime,
} }
#[derive(Serialize, Deserialize)] #[derive(Serialize, Deserialize, FromRow)]
pub struct ReminderCsv { pub struct ReminderCsv {
attachment: Option<Attachment>, attachment: Option<Attachment>,
attachment_name: Option<String>, attachment_name: Option<String>,

View File

@ -9,7 +9,7 @@ WorkingDirectory=/etc/reminder-rs
Restart=always Restart=always
RestartSec=10 RestartSec=10
Environment="RUST_LOG=warn,rocket=info,reminder_rs=debug,postman=debug" Environment="RUST_LOG=warn,rocket=info,reminder_rs=debug,postman=debug"
WatchdogSec=120 WatchdogSec=300
[Install] [Install]
WantedBy=multi-user.target WantedBy=multi-user.target