Ports TG's BSQL library (#26455)

* Ports TG's BSQL

* write perms

* my mistake

* Missing migrations, fixes mistakes, removes unneeded logs

* Missing migrations, fixes mistakes, removes unneeded logs

* haha

* Final missing migration, actually fix runtime

* Fucked up this doesn't throw a warning

* sql fixes; polls
This commit is contained in:
ShiftyRail
2020-05-19 18:35:10 +02:00
committed by GitHub
parent 06b63e6654
commit bbd746ae42
61 changed files with 1638 additions and 883 deletions

7
code/libs/BSQL/LICENSE Normal file
View File

@@ -0,0 +1,7 @@
Copyright 2018 Jordan Brown
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View File

@@ -0,0 +1,68 @@
/datum/BSQL_Connection
var/id
var/connection_type
BSQL_PROTECT_DATUM(/datum/BSQL_Connection)
/datum/BSQL_Connection/New(connection_type, asyncTimeout, blockingTimeout, threadLimit)
if(asyncTimeout == null)
asyncTimeout = BSQL_DEFAULT_TIMEOUT
if(blockingTimeout == null)
blockingTimeout = asyncTimeout
if(threadLimit == null)
threadLimit = BSQL_DEFAULT_THREAD_LIMIT
src.connection_type = connection_type
world._BSQL_InitCheck(src)
var/error = world._BSQL_Internal_Call("CreateConnection", connection_type, "[asyncTimeout]", "[blockingTimeout]", "[threadLimit]")
if(error)
BSQL_ERROR(error)
return
id = world._BSQL_Internal_Call("GetConnection")
if(!id)
BSQL_ERROR("BSQL library failed to provide connect operation for connection id [id]([connection_type])!")
BSQL_DEL_PROC(/datum/BSQL_Connection)
var/error
if(id)
error = world._BSQL_Internal_Call("ReleaseConnection", id)
. = ..()
if(error)
BSQL_ERROR(error)
/datum/BSQL_Connection/BeginConnect(ipaddress, port, username, password, database)
var/error = world._BSQL_Internal_Call("OpenConnection", id, ipaddress, "[port]", username, password, database)
if(error)
BSQL_ERROR(error)
return
var/op_id = world._BSQL_Internal_Call("GetOperation")
if(!op_id)
BSQL_ERROR("Library failed to provide connect operation for connection id [id]([connection_type])!")
return
return new /datum/BSQL_Operation(src, op_id)
/datum/BSQL_Connection/BeginQuery(query)
var/error = world._BSQL_Internal_Call("NewQuery", id, query)
if(error)
BSQL_ERROR(error)
return
var/op_id = world._BSQL_Internal_Call("GetOperation")
if(!op_id)
BSQL_ERROR("Library failed to provide query operation for connection id [id]([connection_type])!")
return
return new /datum/BSQL_Operation/Query(src, op_id)
/datum/BSQL_Connection/Quote(str)
if(!str)
return null;
. = world._BSQL_Internal_Call("QuoteString", id, "[str]")
if(!.)
BSQL_ERROR("Library failed to provide quote for [str]!")

View File

@@ -0,0 +1,43 @@
/world/proc/_BSQL_Internal_Call(func, ...)
var/list/call_args = args.Copy(2)
BSQL_Debug("[.....]: [args[1]]([call_args.Join(", ")])")
. = call(_BSQL_Library_Path(), func)(arglist(call_args))
BSQL_Debug("Result: [. == null ? "NULL" : "\"[.]\""]")
/world/proc/_BSQL_Library_Path()
return system_type == MS_WINDOWS ? "BSQL.dll" : "libBSQL.so"
/world/proc/_BSQL_InitCheck(datum/BSQL_Connection/caller)
var/static/library_initialized = FALSE
if(_BSQL_Initialized())
return
var/libPath = _BSQL_Library_Path()
if(!fexists(libPath))
BSQL_DEL_CALL(caller)
BSQL_ERROR("Could not find [libPath]!")
return
var/version = _BSQL_Internal_Call("Version")
if(version != BSQL_VERSION)
BSQL_DEL_CALL(caller)
BSQL_ERROR("BSQL DMAPI version mismatch! Expected [BSQL_VERSION], got [version == null ? "NULL" : version]!")
return
var/result = _BSQL_Internal_Call("Initialize")
if(result)
BSQL_DEL_CALL(caller)
BSQL_ERROR(result)
return
_BSQL_Initialized(TRUE)
/world/proc/_BSQL_Initialized(new_val)
var/static/bsql_library_initialized = FALSE
if(new_val != null)
bsql_library_initialized = new_val
return bsql_library_initialized
/world/BSQL_Shutdown()
if(!_BSQL_Initialized())
return
_BSQL_Internal_Call("Shutdown")
_BSQL_Initialized(FALSE)

View File

@@ -0,0 +1,47 @@
/datum/BSQL_Operation
var/datum/BSQL_Connection/connection
var/id
BSQL_PROTECT_DATUM(/datum/BSQL_Operation)
/datum/BSQL_Operation/New(datum/BSQL_Connection/connection, id)
src.connection = connection
src.id = id
BSQL_DEL_PROC(/datum/BSQL_Operation)
var/error
if(!BSQL_IS_DELETED(connection))
error = world._BSQL_Internal_Call("ReleaseOperation", connection.id, id)
. = ..()
if(error)
BSQL_ERROR(error)
/datum/BSQL_Operation/IsComplete()
if(BSQL_IS_DELETED(connection))
return TRUE
var/result = world._BSQL_Internal_Call("OpComplete", connection.id, id)
if(!result)
BSQL_ERROR("Error fetching operation [id] for connection [connection.id]!")
return
return result == "DONE"
/datum/BSQL_Operation/GetError()
if(BSQL_IS_DELETED(connection))
return "Connection deleted!"
return world._BSQL_Internal_Call("GetError", connection.id, id)
/datum/BSQL_Operation/GetErrorCode()
if(BSQL_IS_DELETED(connection))
return -2
return text2num(world._BSQL_Internal_Call("GetErrorCode", connection.id, id))
/datum/BSQL_Operation/WaitForCompletion()
if(BSQL_IS_DELETED(connection))
return
var/error = world._BSQL_Internal_Call("BlockOnOperation", connection.id, id)
if(error)
if(error == "Operation timed out!") //match this with the implementation
return FALSE
BSQL_ERROR("Error waiting for operation [id] for connection [connection.id]! [error]")
return
return TRUE

View File

@@ -0,0 +1,35 @@
/datum/BSQL_Operation/Query
var/last_result_json
var/list/last_result
BSQL_PROTECT_DATUM(/datum/BSQL_Operation/Query)
/datum/BSQL_Operation/Query/CurrentRow()
return last_result
/datum/BSQL_Operation/Query/IsComplete()
//whole different ballgame here
if(BSQL_IS_DELETED(connection))
return TRUE
var/result = world._BSQL_Internal_Call("ReadyRow", connection.id, id)
switch(result)
if("DONE")
//load the data
LoadQueryResult()
return TRUE
if("NOTDONE")
return FALSE
else
BSQL_ERROR(result)
/datum/BSQL_Operation/Query/WaitForCompletion()
. = ..()
if(.)
LoadQueryResult()
/datum/BSQL_Operation/Query/proc/LoadQueryResult()
last_result_json = world._BSQL_Internal_Call("GetRow", connection.id, id)
if(last_result_json)
last_result = json_decode(last_result_json)
else
last_result = null

View File

@@ -0,0 +1,4 @@
#include "core\connection.dm"
#include "core\library.dm"
#include "core\operation.dm"
#include "core\query.dm"

View File

@@ -1,38 +0,0 @@
// Contains all of the various defines and constants required by the library.
//cursors
#define Default_Cursor 0
#define Client_Cursor 1
#define Server_Cursor 2
//conversions
#define TEXT_CONV 1
#define RSC_FILE_CONV 2
#define NUMBER_CONV 3
//column flag values:
#define IS_NUMERIC 1
#define IS_BINARY 2
#define IS_NOT_NULL 4
#define IS_PRIMARY_KEY 8
#define IS_UNSIGNED 16
//types
#define TINYINT 1
#define SMALLINT 2
#define MEDIUMINT 3
#define INTEGER 4
#define BIGINT 5
#define DECIMAL 6
#define FLOAT 7
#define DOUBLE 8
#define DATE 9
#define DATETIME 10
#define TIMESTAMP 11
#define TIME 12
#define STRING 13
#define BLOB 14
// TODO: Investigate more recent type additions and see if I can handle them. - Nadrew

View File

@@ -1,179 +0,0 @@
//var/const
//DB_SERVER = "localhost" // This is the location of your MySQL server (localhost is USUALLY fine)
//DB_PORT = 3306 // This is the port your MySQL server is running on (3306 is the default)
DBConnection
var/_db_con // This variable contains a reference to the actual database connection.
var/dbi // This variable is a string containing the DBI MySQL requires.
var/user // This variable contains the username data.
var/password // This variable contains the password data.
var/default_cursor // This contains the default database cursor data.
//server = DB_SERVER // "localhost"
var/server = "localhost"
//port = DB_PORT // 3306
var/port = 3306
DBConnection/New(dbi_handler,username,password_handler,cursor_handler)
src.dbi = dbi_handler
src.user = username
src.password = password_handler
src.default_cursor = cursor_handler
_db_con = _dm_db_new_con()
DBConnection/proc/Connect(dbi_handler=src.dbi,user_handler=src.user,password_handler=src.password,cursor_handler)
//if(!src) return 0
if(!sqllogging || !src)
return 0
cursor_handler = src.default_cursor
if(!cursor_handler)
cursor_handler = Default_Cursor
return _dm_db_connect(_db_con,dbi_handler,user_handler,password_handler,cursor_handler,null)
DBConnection/proc/Disconnect() return _dm_db_close(_db_con)
//IsConnected() return _dm_db_is_connected(_db_con)
DBConnection/proc/IsConnected() return !sqllogging ? 0 : _dm_db_is_connected(_db_con)
DBConnection/proc/Quote(str) return _dm_db_quote(_db_con,str)
DBConnection/proc/ErrorMsg() return _dm_db_error_msg(_db_con)
DBConnection/proc/SelectDB(database_name,dbi)
if(IsConnected())
Disconnect()
//return Connect("[dbi?"[dbi]":"dbi:mysql:[database_name]:[DB_SERVER]:[DB_PORT]"]",user,password)
return Connect("[dbi?"[dbi]":"dbi:mysql:[database_name]:[sqladdress]:[sqlport]"]",user,password)
DBConnection/proc/NewQuery(sql_query,cursor_handler=src.default_cursor) return new/DBQuery(sql_query,src,cursor_handler)
DBQuery
var/closed = 0 // N3X: Explicitly closed?
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[0] // list of data values populated by NextRow()
var/DBConnection/db_connection
var/_db_query
DBQuery/New(sql_query,DBConnection/connection_handler,cursor_handler)
if(sql_query)
src.sql = sql_query
if(connection_handler)
src.db_connection = connection_handler
if(cursor_handler)
src.default_cursor = cursor_handler
_db_query = _dm_db_new_query()
return ..()
// DO NOT CHANGE TO DESTROY() - N3X
DBQuery/Del()
Close()
DBQuery/proc/Connect(DBConnection/connection_handler)
src.db_connection = connection_handler
DBQuery/proc/Execute(sql_query=src.sql,cursor_handler=default_cursor)
Close()
return _dm_db_execute(_db_query,sql_query,db_connection._db_con,cursor_handler,null)
DBQuery/proc/NextRow()
return _dm_db_next_row(_db_query,item,conversions)
DBQuery/proc/RowsAffected()
return _dm_db_rows_affected(_db_query)
DBQuery/proc/RowCount()
return _dm_db_row_count(_db_query)
DBQuery/proc/ErrorMsg()
return _dm_db_error_msg(_db_query)
DBQuery/proc/Columns()
if(!columns)
columns = _dm_db_columns(_db_query,/DBColumn)
return columns
DBQuery/proc/GetRowData()
var/list/columns = Columns()
var/list/results
if(columns.len)
results = list()
for(var/C in columns)
results+=C
var/DBColumn/cur_col = columns[C]
results[C] = src.item[(cur_col.position+1)]
return results
DBQuery/proc/Close()
if(closed)
return // Don't do this twice.
closed=1
item.len = 0
columns = null
conversions = null
return _dm_db_close(_db_query)
DBQuery/proc/Quote(str)
return db_connection.Quote(str)
/* SetConversion(column,conversion)
// This doesn't seem to be doing anything internally...
if(istext(column))
column = columns.Find(column)
if(!conversions)
conversions = new/list(column)
else if(conversions.len < column)
conversions.len = column
conversions[column] = conversion*/
DBColumn
var/name
var/table
var/position // 1-based index into item data
var/sql_type
var/flags
var/length
var/max_length
DBColumn/New(name_handler,table_handler,position_handler,type_handler,flag_handler,length_handler,max_length_handler)
src.name = name_handler
src.table = table_handler
src.position = position_handler
src.sql_type = type_handler
src.flags = flag_handler
src.length = length_handler
src.max_length = max_length_handler
return ..()
DBColumn/proc/SqlTypeName(type_handler=src.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"

View File

@@ -1,45 +0,0 @@
/*
Dantom.DB
Created by BYOND for BYOND, 2002.
Release History:
v0.6 Nov 22, 2013 (Nadrew)
v0.5 Mar 23, 2008 (Nadrew)
v0.4 Mar 19, 2008 (Nadrew)
v0.3 Feb 08, 2006 (Nadrew)
v0.2 Jan 31, 2003 (Dan)
v0.1 Nov 30, 2002 (Dan)
Updates:
v0.6 - Updated the documentation.
Commented out DBQuery.SetConversion(), my tests show it doing absolutely nothing to the resulting data.
Moved the defines into their own file to reduce the clutter in core.dm
Cleaned up the commenting of the code and the code itself a bit more.
v0.5 - Added DBConnection.SelectDB() see db.html for details.
Changed all global constants to quicker #defines.
Added global variables DB_SERVER and DB_PORT to help SelectDB() out.
Moved this information and the core code into seperate files.
v0.4 - Cleaned up the code even more.
Rewrote the argument names for the procs to be less cryptic (you know, Dancode-y).
Added a few comments here and there.
Sped up various procs by "modernizing" some of the code inside of them.
Wrote some actual documentation for the library, since as of 413.00 it should get a bit more usage.
v0.3 - Fixed long-standing bug with the connection process, adding a workaround to a strange BYOND bug.
Updated all of the command arguments to not match local variables, as it was causing tons of issues.
The arguments aren't named very well, but you can tell what they do.
Added GetRowData() function to the DBQuery datum, this function will allow you to obtain a list of
table data referenced by name and not by index number.
v0.2 - Cleaned up the code significantly.
See db.html for documentation.
See core.dm for guts.
See constants.dm for #defines and constants.
*/

View File

@@ -1,23 +0,0 @@
// DM Environment file for Dantom.DB.dme.
// All manual changes should be made outside the BEGIN_ and END_ blocks.
// New source code should be placed in .dm files: choose File/New --> Code File.
// BEGIN_INTERNALS
/*
FILE: db.html
*/
// END_INTERNALS
// BEGIN_FILE_DIR
#define FILE_DIR .
// END_FILE_DIR
// BEGIN_PREFERENCES
#define DEBUG
// END_PREFERENCES

View File

@@ -1,43 +0,0 @@
<html>
<head>
<title>Dantom.DB Documentation</title>
</head>
<body>
<base target="contentframe">
<h2>Dantom.DB</h2>
<hr>
<table width=100% height=100%><tr><td width=25% valign=top>
<ul>
<li><a href=dbinfo.html#DBConnection>DBConnection</a>
<ul>
<li><a href=dbinfo.html#DBConnection.Connect()>Connect()</a></li>
<li><a href=dbinfo.html#DBConnection.Disconnect()>Disconnect()</a></li>
<li><a href=dbinfo.html#DBConnection.IsConnected()>IsConnected()</a></li>
<li><a href=dbinfo.html#DBConnection.Quote()>Quote()</a></li>
<li><a href=dbinfo.html#DBConnection.ErrorMsg()>ErrorMsg()</a></li>
<li><a href=dbinfo.html#DBConnection.SelectDB()>SelectDB()</a></li>
<li><a href=dbinfo.html#DBConnection.NewQuery()>NewQuery()</a></li>
</ul>
</li>
<li><a href=dbinfo.html#DBQuery>DBQuery</a>

View File

@@ -1,142 +0,0 @@
<b>Note:</b> I wrote this document to the best of my knowledge, I still don't know everything there is to know about this library or MySQL. - Nadrew<p>
<h2><a name="DBConnection">DBConnection</a></h2>
The DBConnection datum is what handles your connection to the MySQL database.
<br><b>A note on the DBI string's format, it's "dbi:mysql:[database_name]:[server]:[port]"</b>
<p style="color:red;"><b>If libMySQL (.dll/.so) does not exist on the system DBConnection.ErrorMsg() will return 'The selected module could not be loaded.'</b></p>
<hr>
<font size=+1><b><a name="DBConnection.Connect()">Connect()</a></b></font><br>
Creates a new connection to your MySQL server.<br>
<b>Format:</b> Connect(dbi,username,password,cursor)<br>
<b>Arguments:</b>
<dd><b>dbi</b> The database interface string to send to MySQL.
<dd><b>username</b> The MySQL username to send.
<dd><b>password</b> The MySQL password associated with the username.
<dd><b>cursor</b> The MySQL cursor to connect with, best not to set this.<p>
<b>Example:</b><br>
<pre>
var/DBConnection/dbcon = new()
dbcon.Connect("dbi:mysql:database_name:localhost:3306","username","password")
if(!dbcon.IsConnected()) usr << dbcon.ErrorMsg()
</pre>
<hr>
<font size=+1><b><a name="DBConnection.Disconnect()">Disconnect()</a></b></font><br>
Disconnects the current MySQL database connection.
<hr>
<font size=+1><b><a name="DBConnection.IsConnected()">IsConnected()</a></b></font><br>
Returns true if the DBConnection object has a connection, false otherwise.
<hr>
<font size=+1><b><a name="DBConnection.Quote()">Quote()</a></b></font><br>
This will escape the string passed using the MySQL escaping function, this will prevent against things like data injection attacks<br>
<b>Format:</b> Quote(string)<br>
<b>Arguments:</b>
<dd><b>string</b> The string you want to escape.<hr>
<font size=+1><b><a name="DBConnection.ErrorMsg()">ErrorMsg()</a></b></font><br>
This will return the error message (if any) given by the MySQL server. For an example of the usage, check the example under <a href="#DBConnection.Connect()">Connect()</a>.<hr>
<font size=+1><b><a name="DBConnection.SelectDB()">SelectDB()</a></b></font><br>
This allows you to quickly change the database your DBConnection object is acting on.<br>
<b>Format:</b> SelectDB(database,dbi)<br>
<b>Arguments:</b>
<dd><b>database</b> The database you wish to switch to.
<dd><b>dbi</b> (Optional) The dbi string you want to use to make the switch.<hr>
<font size=+1><b><a name="DBConnection.NewQuery()">NewQuery()</a></b></font><br>
This is a nice handy function that'll handle the creation of new query objects for you and return the resulting object.<br>
<b>Format:</b> NewQuery(query,cursor)<br>
<b>Arguments:</b>
<dd><b>query</b> The SQL query you want to execute.
<dd><b>cursor</b> As usual, best to leave this one alone ;)<br>
<b>Example:</b><br>
<pre>
var/DBQuery/my_query = dbcon.NewQuery("SELECT * FROM `my_table`")
if(my_query.RowCount()) usr << "Got some data."
else usr << "No data there!"
</pre><hr>
<h2><a name="DBQuery">DBQuery</a></h2>
The DBQuery datum is what handles executing SQL queries and contains all of the various data given by that query.
<hr>
<font size=+1><b><a name="DBQuery.Connect()">Connect()</a></b></font><br>
This proc is a way of quickly changing the DBConnection object the query object is connected to.<br>
<b>Format:</b> Connect(DBConnection)<br>
<b>Arguments:</b>
<dd><b>DBConnection</b> The new /DBConnection object to associate with this query.<hr>
<font size=+1><b><a name="DBQuery.Execute()">Execute()</a></b></font><br>
Executes a SQL query and returns true if the query succeded or false otherwise. If no query is passed this will execute the last query sent to the object (which is set if a query is passed to NewQuery())<br>
<b>Format:</b> Execute(query,cursor)<br>
<b>Arguments:</b>
<dd><b>query</b> The SQL query to execute.
<dd><b>cursor</b> Seriously, if you don't know what this is, no touching!<br>
<b>Example:</b><br>
<pre>
var/DBQuery/query = dbcon.NewQuery("SELECT * FROM `my_table`")
if(!query.Execute()) usr << query.ErrorMsg()
else usr << "The query worked!"
</pre><hr>