diff --git a/code/__HELPERS/bandetect.dm b/code/__HELPERS/bandetect.dm
index 9da3c38632..19c0d37838 100644
--- a/code/__HELPERS/bandetect.dm
+++ b/code/__HELPERS/bandetect.dm
@@ -2,7 +2,7 @@
/client/proc/join_date_check(y,m,d)
- var/DBQuery/query_datediff = GLOB.dbcon.NewQuery("SELECT DATEDIFF(Now(),'[y]-[m]-[d]')")
+ var/datum/DBQuery/query_datediff = SSdbcore.NewQuery("SELECT DATEDIFF(Now(),'[y]-[m]-[d]')")
if(!query_datediff.Execute())
return FALSE
diff --git a/code/__HELPERS/text.dm b/code/__HELPERS/text.dm
index f78c40fbe1..c6148bb512 100644
--- a/code/__HELPERS/text.dm
+++ b/code/__HELPERS/text.dm
@@ -15,7 +15,7 @@
// Run all strings to be used in an SQL query through this proc first to properly escape out injection attempts.
/proc/sanitizeSQL(t)
- var/sqltext = GLOB.dbcon.Quote("[t]");
+ var/sqltext = SSdbcore.Quote("[t]");
return copytext(sqltext, 2, lentext(sqltext));//Quote() adds quotes around input, we already do that
/proc/format_table_name(table as text)
diff --git a/code/_globalvars/misc.dm b/code/_globalvars/misc.dm
index a7763236e6..285746cbd7 100644
--- a/code/_globalvars/misc.dm
+++ b/code/_globalvars/misc.dm
@@ -13,9 +13,4 @@ GLOBAL_DATUM(data_core, /datum/datacore)
GLOBAL_VAR_INIT(CELLRATE, 0.002) // multiplier for watts per tick <> cell storage (eg: .002 means if there is a load of 1000 watts, 20 units will be taken from a cell per second)
GLOBAL_VAR_INIT(CHARGELEVEL, 0.001) // Cap for how fast cells charge, as a percentage-per-tick (.001 means cellcharge is capped to 1% per second)
-GLOBAL_LIST_EMPTY(powernets)
-
-//Database connections
-//A connection is established on world creation. Ideally, the connection dies when the server restarts (After feedback logging.).
-GLOBAL_DATUM_INIT(dbcon, /DBConnection, new) //Feedback database (New database)
-GLOBAL_PROTECT(dbcon)
\ No newline at end of file
+GLOBAL_LIST_EMPTY(powernets)
\ No newline at end of file
diff --git a/code/controllers/subsystem/dbcore.dm b/code/controllers/subsystem/dbcore.dm
new file mode 100644
index 0000000000..f2864027f8
--- /dev/null
+++ b/code/controllers/subsystem/dbcore.dm
@@ -0,0 +1,218 @@
+SUBSYSTEM_DEF(dbcore)
+ name = "Database"
+ flags = SS_NO_INIT|SS_NO_FIRE
+
+ var/const/FAILED_DB_CONNECTION_CUTOFF = 5
+
+ var/const/Default_Cursor = 0
+ var/const/Client_Cursor = 1
+ var/const/Server_Cursor = 2
+ //conversions
+ var/const/TEXT_CONV = 1
+ var/const/RSC_FILE_CONV = 2
+ var/const/NUMBER_CONV = 3
+ //column flag values:
+ var/const/IS_NUMERIC = 1
+ var/const/IS_BINARY = 2
+ var/const/IS_NOT_NULL = 4
+ var/const/IS_PRIMARY_KEY = 8
+ var/const/IS_UNSIGNED = 16
+// TODO: Investigate more recent type additions and see if I can handle them. - Nadrew
+
+ var/_db_con// This variable contains a reference to the actual database connection.
+ var/failed_connections = 0
+
+/datum/controller/subsystem/dbcore/PreInit()
+ _db_con = _dm_db_new_con()
+
+/datum/controller/subsystem/dbcore/Recover()
+ _db_con = SSdbcore._db_con
+
+/datum/controller/subsystem/dbcore/Shutdown()
+ if(IsConnected())
+ Disconnect()
+
+//nu
+/datum/controller/subsystem/dbcore/can_vv_get(var_name)
+ return var_name != "_db_con" && ..()
+
+/datum/controller/subsystem/dbcore/vv_edit_var(var_name, var_value)
+ if(var_name == "_db_con")
+ return FALSE
+ return ..()
+
+/datum/controller/subsystem/dbcore/proc/Connect()
+ if(IsConnected())
+ return TRUE
+
+ if(failed_connections > FAILED_DB_CONNECTION_CUTOFF) //If it failed to establish a connection more than 5 times in a row, don't bother attempting to connect anymore.
+ return FALSE
+
+ if(!config.sql_enabled)
+ return FALSE
+
+ var/user = GLOB.sqlfdbklogin
+ var/pass = GLOB.sqlfdbkpass
+ var/db = GLOB.sqlfdbkdb
+ var/address = GLOB.sqladdress
+ var/port = GLOB.sqlport
+
+ doConnect("dbi:mysql:[db]:[address]:[port]", user, pass)
+ . = IsConnected()
+ if (!.)
+ log_sql("Connect() failed | [ErrorMsg()]")
+ ++failed_connections
+
+/datum/controller/subsystem/dbcore/proc/doConnect(dbi_handler, user_handler, password_handler)
+ if(!config.sql_enabled)
+ return FALSE
+ return _dm_db_connect(_db_con, dbi_handler, user_handler, password_handler, Default_Cursor, null)
+
+/datum/controller/subsystem/dbcore/proc/Disconnect()
+ failed_connections = 0
+ return _dm_db_close(_db_con)
+
+/datum/controller/subsystem/dbcore/proc/IsConnected()
+ if(!config.sql_enabled)
+ return FALSE
+ return _dm_db_is_connected(_db_con)
+
+/datum/controller/subsystem/dbcore/proc/Quote(str)
+ return _dm_db_quote(_db_con, str)
+
+/datum/controller/subsystem/dbcore/proc/ErrorMsg()
+ return _dm_db_error_msg(_db_con)
+
+/datum/controller/subsystem/dbcore/proc/NewQuery(sql_query, cursor_handler = Default_Cursor)
+ return new /datum/DBQuery(sql_query, src, cursor_handler)
+
+
+/datum/DBQuery
+ var/sql // The sql query being executed.
+ var/default_cursor
+ var/list/columns //list of DB Columns populated by Columns()
+ var/list/conversions
+ var/list/item //list of data values populated by NextRow()
+
+ var/datum/controller/subsystem/dbcore/db_connection
+ var/_db_query
+
+/datum/DBQuery/New(sql_query, datum/controller/subsystem/dbcore/connection_handler, cursor_handler)
+ if(sql_query)
+ sql = sql_query
+ if(connection_handler)
+ db_connection = connection_handler
+ if(cursor_handler)
+ default_cursor = cursor_handler
+ item = list()
+ _db_query = _dm_db_new_query()
+
+/datum/DBQuery/proc/Connect(datum/controller/subsystem/dbcore/connection_handler)
+ db_connection = connection_handler
+
+/datum/DBQuery/proc/warn_execute()
+ . = Execute()
+ if(!.)
+ to_chat(usr, "A SQL error occured during this operation, check the server logs.")
+
+/datum/DBQuery/proc/Execute(sql_query = sql, cursor_handler = default_cursor, log_error = TRUE)
+ Close()
+ . = _dm_db_execute(_db_query, sql_query, db_connection._db_con, cursor_handler, null)
+ if(!. && log_error)
+ log_sql("[ErrorMsg()] | Query used: [sql]")
+
+/datum/DBQuery/proc/NextRow()
+ return _dm_db_next_row(_db_query,item,conversions)
+
+/datum/DBQuery/proc/RowsAffected()
+ return _dm_db_rows_affected(_db_query)
+
+/datum/DBQuery/proc/RowCount()
+ return _dm_db_row_count(_db_query)
+
+/datum/DBQuery/proc/ErrorMsg()
+ return _dm_db_error_msg(_db_query)
+
+/datum/DBQuery/proc/Columns()
+ if(!columns)
+ columns = _dm_db_columns(_db_query, /datum/DBColumn)
+ return columns
+
+/datum/DBQuery/proc/GetRowData()
+ var/list/columns = Columns()
+ var/list/results
+ if(columns.len)
+ results = list()
+ for(var/C in columns)
+ results+=C
+ var/datum/DBColumn/cur_col = columns[C]
+ results[C] = src.item[(cur_col.position+1)]
+ return results
+
+/datum/DBQuery/proc/Close()
+ item.Cut()
+ columns = null
+ conversions = null
+ return _dm_db_close(_db_query)
+
+/datum/DBQuery/proc/Quote(str)
+ return db_connection.Quote(str)
+
+/datum/DBQuery/proc/SetConversion(column,conversion)
+ if(istext(column))
+ column = columns.Find(column)
+ if(!conversions)
+ conversions = list(column)
+ else if(conversions.len < column)
+ conversions.len = column
+ conversions[column] = conversion
+
+
+/datum/DBColumn
+ var/name
+ var/table
+ var/position //1-based index into item data
+ var/sql_type
+ var/flags
+ var/length
+ var/max_length
+ //types
+ var/const/TINYINT = 1
+ var/const/SMALLINT = 2
+ var/const/MEDIUMINT = 3
+ var/const/INTEGER = 4
+ var/const/BIGINT = 5
+ var/const/DECIMAL = 6
+ var/const/FLOAT = 7
+ var/const/DOUBLE = 8
+ var/const/DATE = 9
+ var/const/DATETIME = 10
+ var/const/TIMESTAMP = 11
+ var/const/TIME = 12
+ var/const/STRING = 13
+ var/const/BLOB = 14
+
+/datum/DBColumn/New(name_handler, table_handler, position_handler, type_handler, flag_handler, length_handler, max_length_handler)
+ name = name_handler
+ table = table_handler
+ position = position_handler
+ sql_type = type_handler
+ flags = flag_handler
+ length = length_handler
+ max_length = max_length_handler
+
+/datum/DBColumn/proc/SqlTypeName(type_handler = sql_type)
+ switch(type_handler)
+ if(TINYINT) return "TINYINT"
+ if(SMALLINT) return "SMALLINT"
+ if(MEDIUMINT) return "MEDIUMINT"
+ if(INTEGER) return "INTEGER"
+ if(BIGINT) return "BIGINT"
+ if(FLOAT) return "FLOAT"
+ if(DOUBLE) return "DOUBLE"
+ if(DATE) return "DATE"
+ if(DATETIME) return "DATETIME"
+ if(TIMESTAMP) return "TIMESTAMP"
+ if(TIME) return "TIME"
+ if(STRING) return "STRING"
+ if(BLOB) return "BLOB"
diff --git a/code/modules/admin/DB_ban/functions.dm b/code/modules/admin/DB_ban/functions.dm
index d6d88b6e8f..92665c06a5 100644
--- a/code/modules/admin/DB_ban/functions.dm
+++ b/code/modules/admin/DB_ban/functions.dm
@@ -6,7 +6,7 @@
if(!check_rights(R_BAN))
return
- if(!GLOB.dbcon.Connect())
+ if(!SSdbcore.Connect())
to_chat(src, "Failed to establish database connection.")
return
@@ -71,7 +71,7 @@
computerid = bancid
ip = banip
- var/DBQuery/query_add_ban_get_id = GLOB.dbcon.NewQuery("SELECT id FROM [format_table_name("player")] WHERE ckey = '[ckey]'")
+ var/datum/DBQuery/query_add_ban_get_id = SSdbcore.NewQuery("SELECT id FROM [format_table_name("player")] WHERE ckey = '[ckey]'")
if(!query_add_ban_get_id.warn_execute())
return
var/validckey = 0
@@ -113,7 +113,7 @@
reason = sanitizeSQL(reason)
if(maxadminbancheck)
- var/DBQuery/query_check_adminban_amt = GLOB.dbcon.NewQuery("SELECT count(id) AS num FROM [format_table_name("ban")] WHERE (a_ckey = '[a_ckey]') AND (bantype = 'ADMIN_PERMABAN' OR (bantype = 'ADMIN_TEMPBAN' AND expiration_time > Now())) AND isnull(unbanned)")
+ var/datum/DBQuery/query_check_adminban_amt = SSdbcore.NewQuery("SELECT count(id) AS num FROM [format_table_name("ban")] WHERE (a_ckey = '[a_ckey]') AND (bantype = 'ADMIN_PERMABAN' OR (bantype = 'ADMIN_TEMPBAN' AND expiration_time > Now())) AND isnull(unbanned)")
if(!query_check_adminban_amt.warn_execute())
return
if(query_check_adminban_amt.NextRow())
@@ -126,7 +126,7 @@
if(!ip)
ip = "0.0.0.0"
var/sql = "INSERT INTO [format_table_name("ban")] (`bantime`,`server_ip`,`server_port`,`bantype`,`reason`,`job`,`duration`,`expiration_time`,`ckey`,`computerid`,`ip`,`a_ckey`,`a_computerid`,`a_ip`,`who`,`adminwho`) VALUES (Now(), INET_ATON('[world.internet_address]'), '[world.port]', '[bantype_str]', '[reason]', '[job]', [(duration)?"[duration]":"0"], Now() + INTERVAL [(duration>0) ? duration : 0] MINUTE, '[ckey]', '[computerid]', INET_ATON('[ip]'), '[a_ckey]', '[a_computerid]', INET_ATON('[a_ip]'), '[who]', '[adminwho]')"
- var/DBQuery/query_add_ban = GLOB.dbcon.NewQuery(sql)
+ var/datum/DBQuery/query_add_ban = SSdbcore.NewQuery(sql)
if(!query_add_ban.warn_execute())
return
to_chat(usr, "Ban saved to database.")
@@ -187,13 +187,13 @@
if(job)
sql += " AND job = '[job]'"
- if(!GLOB.dbcon.Connect())
+ if(!SSdbcore.Connect())
return
var/ban_id
var/ban_number = 0 //failsafe
- var/DBQuery/query_unban_get_id = GLOB.dbcon.NewQuery(sql)
+ var/datum/DBQuery/query_unban_get_id = SSdbcore.NewQuery(sql)
if(!query_unban_get_id.warn_execute())
return
while(query_unban_get_id.NextRow())
@@ -225,7 +225,7 @@
to_chat(usr, "Cancelled")
return
- var/DBQuery/query_edit_ban_get_details = GLOB.dbcon.NewQuery("SELECT ckey, duration, reason FROM [format_table_name("ban")] WHERE id = [banid]")
+ var/datum/DBQuery/query_edit_ban_get_details = SSdbcore.NewQuery("SELECT ckey, duration, reason FROM [format_table_name("ban")] WHERE id = [banid]")
if(!query_edit_ban_get_details.warn_execute())
return
@@ -254,7 +254,7 @@
to_chat(usr, "Cancelled")
return
- var/DBQuery/query_edit_ban_reason = GLOB.dbcon.NewQuery("UPDATE [format_table_name("ban")] SET reason = '[value]', edits = CONCAT(edits,'- [eckey] changed ban reason from \\\"[reason]\\\" to \\\"[value]\\\"
') WHERE id = [banid]")
+ var/datum/DBQuery/query_edit_ban_reason = SSdbcore.NewQuery("UPDATE [format_table_name("ban")] SET reason = '[value]', edits = CONCAT(edits,'- [eckey] changed ban reason from \\\"[reason]\\\" to \\\"[value]\\\"
') WHERE id = [banid]")
if(!query_edit_ban_reason.warn_execute())
return
message_admins("[key_name_admin(usr)] has edited a ban for [pckey]'s reason from [reason] to [value]",1)
@@ -265,7 +265,7 @@
to_chat(usr, "Cancelled")
return
- var/DBQuery/query_edit_ban_duration = GLOB.dbcon.NewQuery("UPDATE [format_table_name("ban")] SET duration = [value], edits = CONCAT(edits,'- [eckey] changed ban duration from [duration] to [value]
'), expiration_time = DATE_ADD(bantime, INTERVAL [value] MINUTE) WHERE id = [banid]")
+ var/datum/DBQuery/query_edit_ban_duration = SSdbcore.NewQuery("UPDATE [format_table_name("ban")] SET duration = [value], edits = CONCAT(edits,'- [eckey] changed ban duration from [duration] to [value]
'), expiration_time = DATE_ADD(bantime, INTERVAL [value] MINUTE) WHERE id = [banid]")
if(!query_edit_ban_duration.warn_execute())
return
message_admins("[key_name_admin(usr)] has edited a ban for [pckey]'s duration from [duration] to [value]",1)
@@ -287,13 +287,13 @@
var/sql = "SELECT ckey FROM [format_table_name("ban")] WHERE id = [id]"
- if(!GLOB.dbcon.Connect())
+ if(!SSdbcore.Connect())
return
var/ban_number = 0 //failsafe
var/pckey
- var/DBQuery/query_unban_get_ckey = GLOB.dbcon.NewQuery(sql)
+ var/datum/DBQuery/query_unban_get_ckey = SSdbcore.NewQuery(sql)
if(!query_unban_get_ckey.warn_execute())
return
while(query_unban_get_ckey.NextRow())
@@ -316,7 +316,7 @@
var/unban_ip = src.owner:address
var/sql_update = "UPDATE [format_table_name("ban")] SET unbanned = 1, unbanned_datetime = Now(), unbanned_ckey = '[unban_ckey]', unbanned_computerid = '[unban_computerid]', unbanned_ip = INET_ATON('[unban_ip]') WHERE id = [id]"
- var/DBQuery/query_unban = GLOB.dbcon.NewQuery(sql_update)
+ var/datum/DBQuery/query_unban = SSdbcore.NewQuery(sql_update)
if(!query_unban.warn_execute())
return
message_admins("[key_name_admin(usr)] has lifted [pckey]'s ban.",1)
@@ -339,7 +339,7 @@
if(!check_rights(R_BAN))
return
- if(!GLOB.dbcon.Connect())
+ if(!SSdbcore.Connect())
to_chat(usr, "Failed to establish database connection.")
return
@@ -405,7 +405,7 @@
var/bansperpage = 15
var/pagecount = 0
page = text2num(page)
- var/DBQuery/query_count_bans = GLOB.dbcon.NewQuery("SELECT COUNT(id) FROM [format_table_name("ban")] WHERE 1 [playersearch] [adminsearch]")
+ var/datum/DBQuery/query_count_bans = SSdbcore.NewQuery("SELECT COUNT(id) FROM [format_table_name("ban")] WHERE 1 [playersearch] [adminsearch]")
if(!query_count_bans.warn_execute())
return
if(query_count_bans.NextRow())
@@ -431,7 +431,7 @@
output += "
| AUTHOR | TITLE | CATEGORY | SS13BN |