From cd065ed6ba23a0767a295df8209f3fc05a99d302 Mon Sep 17 00:00:00 2001 From: Werner <1331699+Arrow768@users.noreply.github.com> Date: Mon, 10 Nov 2025 08:40:41 +0100 Subject: [PATCH] Change SSPRISM to use the new DBCore (#21543) Ported almost all of it from TG Co-authored-by: Werner --- code/controllers/subsystems/dbcore.dm | 103 ++++++++++++++++++++++- code/controllers/subsystems/stickyban.dm | 58 ++++++++----- html/changelogs/arrow768-ssstickyban.yml | 58 +++++++++++++ 3 files changed, 197 insertions(+), 22 deletions(-) create mode 100644 html/changelogs/arrow768-ssstickyban.yml diff --git a/code/controllers/subsystems/dbcore.dm b/code/controllers/subsystems/dbcore.dm index 14c10c783c9..cb85832882c 100644 --- a/code/controllers/subsystems/dbcore.dm +++ b/code/controllers/subsystems/dbcore.dm @@ -370,7 +370,7 @@ SUBSYSTEM_DEF(dbcore) * */ /datum/db_query_template/proc/Execute(arguments, allow_during_shutdown=FALSE) - var/datum/db_query/query = SSdbcore.NewQuery(sql, arguments, allow_during_shutdown) + var/datum/db_query/query = SSdbcore.NewQuery(sql, arguments, allow_during_shutdown) return query.Execute() /datum/db_query @@ -548,6 +548,107 @@ SUBSYSTEM_DEF(dbcore) while(status < DB_QUERY_FINISHED) stoplag() + +/** QuerySelect + Run a list of query datums in parallel, blocking until they all complete. + * queries - List of queries or single query datum to run. + * warn - Controls rather warn_execute() or Execute() is called. + * qdel - If you don't care about the result or checking for errors, you can have the queries be deleted afterwards. + This can be combined with invoke_async as a way of running queries async without having to care about waiting for them to finish so they can be deleted, + however you should probably just use FireAndForget instead if it's just a single query. +*/ +/datum/controller/subsystem/dbcore/proc/QuerySelect(list/queries, warn = FALSE, qdel = FALSE) + if (!islist(queries)) + if (!istype(queries, /datum/db_query)) + CRASH("Invalid query passed to QuerySelect: [queries]") + queries = list(queries) + else + queries = queries.Copy() //we don't want to hide bugs in the parent caller by removing invalid values from this list. + + for (var/datum/db_query/query as anything in queries) + if (!istype(query)) + queries -= query + stack_trace("Invalid query passed to QuerySelect: `[query]` [REF(query)]") + continue + + if (warn) + INVOKE_ASYNC(query, TYPE_PROC_REF(/datum/db_query, warn_execute)) + else + INVOKE_ASYNC(query, TYPE_PROC_REF(/datum/db_query, Execute)) + + for (var/datum/db_query/query as anything in queries) + query.sync() + if (qdel) + qdel(query) + +/* +Takes a list of rows (each row being an associated list of column => value) and inserts them via a single mass query. +Rows missing columns present in other rows will resolve to SQL NULL +You are expected to do your own escaping of the data, and expected to provide your own quotes for strings. +The duplicate_key arg can be true to automatically generate this part of the query + or set to a string that is appended to the end of the query +Ignore_errors instructes mysql to continue inserting rows if some of them have errors. + the erroneous row(s) aren't inserted and there isn't really any way to know why or why errored +*/ +/datum/controller/subsystem/dbcore/proc/MassInsert(table, list/rows, duplicate_key = FALSE, ignore_errors = FALSE, warn = FALSE, async = TRUE, special_columns = null) + if (!table || !rows || !istype(rows)) + return + + // Prepare column list + var/list/columns = list() + var/list/has_question_mark = list() + for (var/list/row in rows) + for (var/column in row) + columns[column] = "?" + has_question_mark[column] = TRUE + for (var/column in special_columns) + columns[column] = special_columns[column] + has_question_mark[column] = findtext(special_columns[column], "?") + + // Prepare SQL query full of placeholders + var/list/query_parts = list("INSERT") + if (ignore_errors) + query_parts += " IGNORE" + query_parts += " INTO " + query_parts += table + query_parts += "\n([columns.Join(", ")])\nVALUES" + + var/list/arguments = list() + var/has_row = FALSE + for (var/list/row in rows) + if (has_row) + query_parts += "," + query_parts += "\n (" + var/has_col = FALSE + for (var/column in columns) + if (has_col) + query_parts += ", " + if (has_question_mark[column]) + var/name = "p[arguments.len]" + query_parts += replacetext(columns[column], "?", ":[name]") + arguments[name] = row[column] + else + query_parts += columns[column] + has_col = TRUE + query_parts += ")" + has_row = TRUE + + if (duplicate_key == TRUE) + var/list/column_list = list() + for (var/column in columns) + column_list += "[column] = VALUES([column])" + query_parts += "\nON DUPLICATE KEY UPDATE [column_list.Join(", ")]" + else if (duplicate_key != FALSE) + query_parts += duplicate_key + + var/datum/db_query/Query = NewQuery(query_parts.Join(), arguments) + if (warn) + . = Query.warn_execute(async) + else + . = Query.Execute(async) + qdel(Query) + + /** * process * diff --git a/code/controllers/subsystems/stickyban.dm b/code/controllers/subsystems/stickyban.dm index 82388bc0291..38e1fcab0ed 100644 --- a/code/controllers/subsystems/stickyban.dm +++ b/code/controllers/subsystems/stickyban.dm @@ -53,12 +53,18 @@ SUBSYSTEM_DEF(stickyban) /datum/controller/subsystem/stickyban/proc/Populatedbcache() var/newdbcache = list() //so if we runtime or the db connection dies we don't kill the existing cache - var/DBQuery/query_stickybans = GLOB.dbcon.NewQuery("SELECT ckey, reason, banning_admin, datetime FROM ss13_stickyban ORDER BY ckey") - var/DBQuery/query_ckey_matches = GLOB.dbcon.NewQuery("SELECT stickyban, matched_ckey, first_matched, last_matched, exempt FROM ss13_stickyban_matched_ckey ORDER BY first_matched") - var/DBQuery/query_cid_matches = GLOB.dbcon.NewQuery("SELECT stickyban, matched_cid, first_matched, last_matched FROM ss13_stickyban_matched_cid ORDER BY first_matched") - var/DBQuery/query_ip_matches = GLOB.dbcon.NewQuery("SELECT stickyban, INET_NTOA(matched_ip), first_matched, last_matched FROM ss13_stickyban_matched_ip ORDER BY first_matched") + var/datum/db_query/query_stickybans = SSdbcore.NewQuery("SELECT ckey, reason, banning_admin, datetime FROM ss13_stickyban ORDER BY ckey") + var/datum/db_query/query_ckey_matches = SSdbcore.NewQuery("SELECT stickyban, matched_ckey, first_matched, last_matched, exempt FROM ss13_stickyban_matched_ckey ORDER BY first_matched") + var/datum/db_query/query_cid_matches = SSdbcore.NewQuery("SELECT stickyban, matched_cid, first_matched, last_matched FROM ss13_stickyban_matched_cid ORDER BY first_matched") + var/datum/db_query/query_ip_matches = SSdbcore.NewQuery("SELECT stickyban, INET_NTOA(matched_ip), first_matched, last_matched FROM ss13_stickyban_matched_ip ORDER BY first_matched") - if (!query_stickybans.Execute()) + SSdbcore.QuerySelect(list(query_stickybans, query_ckey_matches, query_cid_matches, query_ip_matches)) + + if (query_stickybans.last_error) + qdel(query_stickybans) + qdel(query_ckey_matches) + qdel(query_cid_matches) + qdel(query_ip_matches) return while (query_stickybans.NextRow()) @@ -74,7 +80,7 @@ SUBSYSTEM_DEF(stickyban) newdbcache["[query_stickybans.item[1]]"] = ban - if (query_ckey_matches.Execute()) + if (!query_ckey_matches.last_error) while (query_ckey_matches.NextRow()) var/list/match = list() @@ -92,7 +98,7 @@ SUBSYSTEM_DEF(stickyban) keys = ban[text2num(query_ckey_matches.item[5]) ? "whitelist" : "keys"] = list() keys[query_ckey_matches.item[2]] = match - if (query_cid_matches.Execute()) + if (!query_cid_matches.last_error) while (query_cid_matches.NextRow()) var/list/match = list() @@ -110,7 +116,7 @@ SUBSYSTEM_DEF(stickyban) computer_ids[query_cid_matches.item[2]] = match - if (query_ip_matches.Execute()) + if (!query_ip_matches.last_error) while (query_ip_matches.NextRow()) var/list/match = list() @@ -130,6 +136,11 @@ SUBSYSTEM_DEF(stickyban) dbcache = newdbcache dbcacheexpire = world.time+STICKYBAN_DB_CACHE_TIME + qdel(query_stickybans) + qdel(query_ckey_matches) + qdel(query_cid_matches) + qdel(query_ip_matches) + /datum/controller/subsystem/stickyban/proc/import_raw_stickyban_to_db(ckey, list/ban) . = FALSE if (!ban["admin"]) @@ -137,9 +148,14 @@ SUBSYSTEM_DEF(stickyban) if (!ban["message"]) ban["message"] = "Evasion" - var/DBQuery/query_create_stickyban = GLOB.dbcon.NewQuery("INSERT IGNORE INTO ss13_stickyban (ckey, reason, banning_admin) VALUES ('[sanitizeSQL(ckey)]', '[sanitizeSQL(ban["message"])]', '[sanitizeSQL(ban["admin"])]')") - if (!query_create_stickyban.Execute()) + var/datum/db_query/query_create_stickyban = SSdbcore.NewQuery( + "INSERT IGNORE INTO ss13_stickyban (ckey, reason, banning_admin) VALUES (:ckey, :message, :admin)", + list("ckey" = ckey, "message" = ban["message"], "admin" = ban["admin"]) + ) + if (!query_create_stickyban.warn_execute()) + qdel(query_create_stickyban) return + qdel(query_create_stickyban) var/list/sqlckeys = list() var/list/sqlcids = list() @@ -149,8 +165,8 @@ SUBSYSTEM_DEF(stickyban) var/list/keys = splittext(ban["keys"], ",") for (var/key in keys) var/list/sqlckey = list() - sqlckey["stickyban"] = "'[sanitizeSQL(ckey)]'" - sqlckey["matched_ckey"] = "'[sanitizeSQL(ckey(key))]'" + sqlckey["stickyban"] = ckey + sqlckey["matched_ckey"] = ckey(key) sqlckey["exempt"] = FALSE sqlckeys[++sqlckeys.len] = sqlckey @@ -158,8 +174,8 @@ SUBSYSTEM_DEF(stickyban) var/list/keys = splittext(ban["whitelist"], ",") for (var/key in keys) var/list/sqlckey = list() - sqlckey["stickyban"] = "'[sanitizeSQL(ckey)]'" - sqlckey["matched_ckey"] = "'[sanitizeSQL(ckey(key))]'" + sqlckey["stickyban"] = ckey + sqlckey["matched_ckey"] = ckey(key) sqlckey["exempt"] = TRUE sqlckeys[++sqlckeys.len] = sqlckey @@ -167,26 +183,26 @@ SUBSYSTEM_DEF(stickyban) var/list/cids = splittext(ban["computer_id"], ",") for (var/cid in cids) var/list/sqlcid = list() - sqlcid["stickyban"] = "'[sanitizeSQL(ckey)]'" - sqlcid["matched_cid"] = "'[sanitizeSQL(cid)]'" + sqlcid["stickyban"] = ckey + sqlcid["matched_cid"] = cid sqlcids[++sqlcids.len] = sqlcid if (ban["IP"]) var/list/ips = splittext(ban["IP"], ",") for (var/ip in ips) var/list/sqlip = list() - sqlip["stickyban"] = "'[sanitizeSQL(ckey)]'" - sqlip["matched_ip"] = "'[sanitizeSQL(ip)]'" + sqlip["stickyban"] = ckey + sqlip["matched_ip"] = ip sqlips[++sqlips.len] = sqlip if (length(sqlckeys)) - GLOB.dbcon.MassInsert("ss13_stickyban_matched_ckey", sqlckeys, FALSE, TRUE) + SSdbcore.MassInsert("ss13_stickyban_matched_ckey", sqlckeys, ignore_errors = TRUE) if (length(sqlcids)) - GLOB.dbcon.MassInsert("ss13_stickyban_matched_cid", sqlcids, FALSE, TRUE) + SSdbcore.MassInsert("ss13_stickyban_matched_cid", sqlcids, ignore_errors = TRUE) if (length(sqlips)) - GLOB.dbcon.MassInsert("ss13_stickyban_matched_ip", sqlips, FALSE, TRUE) + SSdbcore.MassInsert("ss13_stickyban_matched_ip", sqlips, ignore_errors = TRUE) return TRUE diff --git a/html/changelogs/arrow768-ssstickyban.yml b/html/changelogs/arrow768-ssstickyban.yml new file mode 100644 index 00000000000..b0b4bd80dd9 --- /dev/null +++ b/html/changelogs/arrow768-ssstickyban.yml @@ -0,0 +1,58 @@ +################################ +# Example Changelog File +# +# Note: This file, and files beginning with ".", and files that don't end in ".yml" will not be read. If you change this file, you will look really dumb. +# +# Your changelog will be merged with a master changelog. (New stuff added only, and only on the date entry for the day it was merged.) +# When it is, any changes listed below will disappear. +# +# Valid Prefixes: +# bugfix +# - (fixes bugs) +# wip +# - (work in progress) +# qol +# - (quality of life) +# soundadd +# - (adds a sound) +# sounddel +# - (removes a sound) +# rscadd +# - (adds a feature) +# rscdel +# - (removes a feature) +# imageadd +# - (adds an image or sprite) +# imagedel +# - (removes an image or sprite) +# spellcheck +# - (fixes spelling or grammar) +# experiment +# - (experimental change) +# balance +# - (balance changes) +# code_imp +# - (misc internal code change) +# refactor +# - (refactors code) +# config +# - (makes a change to the config files) +# admin +# - (makes changes to administrator tools) +# server +# - (miscellaneous changes to server) +################################# + +# Your name. +author: arrow768 + +# Optional: Remove this file after generating master changelog. Useful for PR changelogs that won't get used again. +delete-after: True + +# Any changes you've made. See valid prefix list above. +# INDENT WITH TWO SPACES. NOT TABS. SPACES. +# SCREW THIS UP AND IT WON'T WORK. +# Also, this gets changed to [] after reading. Just remove the brackets when you add new shit. +# Please surround your changes in double quotes ("). It works without them, but if you use certain characters it screws up compiling. The quotes will not show up in the changelog. +changes: +- server: "Changed the PRISM Subsystem to use the new DBCore"