diff --git a/.github/guides/NDATABASE.md b/.github/guides/NDATABASE.md index bebd6ff47eca..61828974504c 100644 --- a/.github/guides/NDATABASE.md +++ b/.github/guides/NDATABASE.md @@ -25,7 +25,7 @@ TBD TBD ### Filters TBD -# Query +## Query ## Read Single (read_single) Executes the given query, ## Connection @@ -109,3 +109,9 @@ BYOND internal database driver: http://www.byond.com/docs/ref/#/database Uses SQLite as its DB > A /database datum gives you the ability to create or access a database using SQLite +# Database Operations +## Select +## Delete +## Update +## Insert + diff --git a/code/__DEFINES/text.dm b/code/__DEFINES/text.dm index 26567be26255..d5c6d6c348c5 100644 --- a/code/__DEFINES/text.dm +++ b/code/__DEFINES/text.dm @@ -24,3 +24,6 @@ #define MAX_PAPER_MESSAGE_LEN 3072 #define MAX_BOOK_MESSAGE_LEN 9216 #define MAX_NAME_LEN 28 + +#define COPY_FROM_START(T, loc) copytext(T, 1, loc) +#define COPY_AFTER_FOUND(T, loc) copytext(T, loc+1) diff --git a/code/datums/_ndatabase/code/brsql_adapter.dm b/code/datums/_ndatabase/code/brsql_adapter.dm index d8947dd7a74c..98683c1b054f 100644 --- a/code/datums/_ndatabase/code/brsql_adapter.dm +++ b/code/datums/_ndatabase/code/brsql_adapter.dm @@ -19,80 +19,24 @@ // 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. -#define BRSQL_SYSTABLENAME "__entity_master" -#define BRSQL_SYSINDEXNAME "__index_master" -#define BRSQL_BACKUP_PREFIX "__backup_" -#define BRSQL_COUNT_COLUMN_NAME "total" -#define BRSQL_ROOT_NAME "__root" -#define BRSQL_ROOT_ALIAS "T_root" -#define COPY_FROM_START(T, loc) copytext(T, 1, loc) -#define COPY_AFTER_FOUND(T, loc) copytext(T, loc+1) - -/datum/db/adapter/brsql_adapter - var/list/issue_log + +/datum/db/adapter/sql/brsql var/datum/db/connection/brsql_connection/connection - var/list/datum/db/brsql_cached_query/cached_queries - -/datum/db/adapter/brsql_adapter/New() - issue_log = list() - cached_queries = list() - -// this implementation is pretty janky, our goal here to create query we can work with, not cover every possible case. -// don't make same mistake I did. 20% jank code that is usable now is better than 0% jank code that covers every case that won't even happen and is written in a year -/datum/db/brsql_cached_query - var/datum/db/adapter/brsql_adapter/adapter - // text of the query before we put filter - var/pre_filter - // text of the query after we put filter - var/post_filter - // casts, because our query is already mapped to internal `T_n` structure, while whatever the hell we get is not - var/list/casts = list() - // list of pre parameters - var/list/pre_pflds - // list of post parameters - var/list/post_pflds - - -/datum/db/brsql_cached_query/New(_adapter, _pre_filter, _post_filter, _casts, _pre_pflds, _post_pflds) - adapter = _adapter - pre_filter = _pre_filter - post_filter = _post_filter - casts = _casts - pre_pflds = _pre_pflds - post_pflds = _post_pflds - -// maybe parameters can be changed to named parameters? but that might take time. The contract is set, so changes here shouldn't affect your code besides performance -/datum/db/brsql_cached_query/proc/spawn_query(datum/db/filter/F, list/parameters) - for(var/i in pre_pflds) - parameters.Add(i) - var/query = "[pre_filter] [adapter.get_filter(F, casts, parameters)] [post_filter]" - for(var/i in post_pflds) - parameters.Add(i) - return query - -/datum/db/adapter/brsql_adapter/sync_table_meta() - var/query = getquery_systable_check() +/datum/db/adapter/sql/brsql/sync_table_meta() + var/query = generate_systable_check_sql() var/datum/db/query_response/sys_table = SSdatabase.create_query_sync(query) if(sys_table.status != DB_QUERY_FINISHED) issue_log += "Unable to create or access system table, error: '[sys_table.error]'" return FALSE // OH SHIT OH FUCK - query = getquery_sysindex_check() + query = generate_sysindex_check_sql() var/datum/db/query_response/sys_index = SSdatabase.create_query_sync(query) if(sys_index.status != DB_QUERY_FINISHED) issue_log += "Unable to create or access system index table, error: '[sys_table.error]'" return FALSE // OH SHIT OH FUCK return TRUE - -/datum/db/adapter/brsql_adapter/read_table(table_name, list/ids, datum/callback/on_read_table_callback, sync = FALSE) - var/query_gettable = getquery_select_table(table_name, ids) - if(sync) - SSdatabase.create_query_sync(query_gettable, on_read_table_callback) - else - SSdatabase.create_query(query_gettable, on_read_table_callback) - -/datum/db/adapter/brsql_adapter/update_table(table_name, list/values, datum/callback/on_update_table_callback, sync = FALSE) +/datum/db/adapter/sql/brsql/update_table(table_name, list/values, datum/callback/on_update_table_callback, sync = FALSE) var/list/query_parameters = list() var/query_updatetable = getquery_update_table(table_name, values, query_parameters) if(sync) @@ -100,80 +44,29 @@ else SSdatabase.create_parametric_query(query_updatetable, query_parameters, on_update_table_callback) -/datum/db/adapter/brsql_adapter/insert_table(table_name, list/values, datum/callback/on_insert_table_callback, sync = FALSE) +/datum/db/adapter/sql/brsql/insert_table(table_name, list/values, datum/callback/on_insert_table_callback, sync = FALSE) set waitfor = FALSE var/length = length(values) var/list/query_parameters = list() - var/query_inserttable = getquery_insert_table(table_name, values, query_parameters) - var/datum/callback/on_insert_table_callback_wrapper = CALLBACK(src, TYPE_PROC_REF(/datum/db/adapter/brsql_adapter, after_insert_table), on_insert_table_callback, length, table_name) + var/query_inserttable = generate_insert_table_sql(table_name, values, query_parameters) + var/datum/callback/on_insert_table_callback_wrapper = CALLBACK(src, TYPE_PROC_REF(/datum/db/adapter/sql/brsql, after_insert_table), on_insert_table_callback, length, table_name) if(sync) SSdatabase.create_parametric_query_sync(query_inserttable, query_parameters, on_insert_table_callback_wrapper) else SSdatabase.create_parametric_query(query_inserttable, query_parameters, on_insert_table_callback_wrapper) /// Wrapper to on_insert_table_callback that passes the only value it cares about: the id of the newly inserted table -/datum/db/adapter/brsql_adapter/proc/after_insert_table(datum/callback/on_insert_table_callback, length, table_name, uid, list/results, datum/db/query/brsql/query) +/datum/db/adapter/sql/brsql/proc/after_insert_table(datum/callback/on_insert_table_callback, length, table_name, uid, list/results, datum/db/query/brsql/query) on_insert_table_callback.Invoke(query.last_insert_id) -/datum/db/adapter/brsql_adapter/delete_table(table_name, list/ids, datum/callback/on_delete_table_callback, sync = FALSE) - var/query_deletetable = getquery_delete_table(table_name, ids) - if(sync) - SSdatabase.create_query_sync(query_deletetable, on_delete_table_callback) - else - SSdatabase.create_query(query_deletetable, on_delete_table_callback) - -/datum/db/adapter/brsql_adapter/read_filter(table_name, datum/db/filter, datum/callback/on_read_filter_callback, sync = FALSE) - var/list/query_parameters = list() - var/query_gettable = getquery_filter_table(table_name, filter, query_parameters) - if(sync) - SSdatabase.create_parametric_query_sync(query_gettable, query_parameters, on_read_filter_callback) - else - SSdatabase.create_parametric_query(query_gettable, query_parameters, on_read_filter_callback) - -/datum/db/adapter/brsql_adapter/read_view(datum/entity_view_meta/view, datum/db/filter/filter, datum/callback/on_read_view_callback, sync=FALSE) - var/v_key = "v_[view.type]" - var/list/query_parameters = list() - var/datum/db/brsql_cached_query/cached_view = cached_queries[v_key] - if(!istype(cached_view)) - return null - var/query_getview = cached_view.spawn_query(filter, query_parameters) - if(sync) - SSdatabase.create_parametric_query_sync(query_getview, query_parameters, on_read_view_callback) - else - SSdatabase.create_parametric_query(query_getview, query_parameters, on_read_view_callback) - -/datum/db/adapter/brsql_adapter/sync_table(type_name, table_name, list/datum/db_field/field_typepaths) - var/list/query_parameters = list() - var/query_gettable = getquery_systable_gettable(table_name, query_parameters) - var/datum/db/query_response/table_meta = SSdatabase.create_parametric_query_sync(query_gettable, query_parameters) - if(table_meta.status != DB_QUERY_FINISHED) - issue_log += "Unable to access system table, error: '[table_meta.error]'" - return FALSE // OH SHIT OH FUCK - if(!length(table_meta.results)) // Table doesn't exist - return internal_create_table(table_name, field_typepaths) && internal_record_table_in_sys(type_name, table_name, field_typepaths) - - var/id = table_meta.results[1][DB_DEFAULT_ID_FIELD] - var/old_field_typepaths = savetext2fields(table_meta.results[1]["fields_current"]) - var/old_hash = table_meta.results[1]["fields_hash"] - var/field_text = fields2savetext(field_typepaths) - var/new_hash = sha1(field_text) - - if(old_hash == new_hash) - return TRUE // no action required - - var/tablecount = internal_table_count(table_name) - // check if we have any records - if(tablecount == 0) - // just MURDER IT - return internal_drop_table(table_name) && internal_create_table(table_name, field_typepaths) && internal_record_table_in_sys(type_name, table_name, field_typepaths, id) - +/datum/db/adapter/sql/brsql/complete_sync(type_name, table_name, field_typepaths, old_field_typepaths, id) return internal_drop_backup_table(table_name) && internal_create_backup_table(table_name, old_field_typepaths) && internal_migrate_to_backup(table_name, old_field_typepaths) && \ internal_update_table(table_name, field_typepaths, old_field_typepaths) && internal_record_table_in_sys(type_name, table_name, field_typepaths, id) -/datum/db/adapter/brsql_adapter/sync_index(index_name, table_name, list/fields, unique, cluster) +/datum/db/adapter/sql/brsql/sync_index(index_name, table_name, list/fields, unique, cluster) var/list/query_parameters = list() - var/query_getindex = getquery_sysindex_getindex(index_name, table_name, query_parameters) + var/query_getindex = generate_get_index_sql(index_name, table_name, query_parameters) var/datum/db/query_response/index_meta = SSdatabase.create_parametric_query_sync(query_getindex, query_parameters) if(index_meta.status != DB_QUERY_FINISHED) issue_log += "Unable to access system index table, error: '[index_meta.error]'" @@ -192,34 +85,17 @@ // Index can be updated only by recreating it return internal_drop_index(index_name, table_name) && internal_create_index(index_name, table_name, fields, unique, cluster) && internal_record_index_in_sys(index_name, table_name, fields, id) -/datum/db/adapter/brsql_adapter/proc/internal_create_table(table_name, field_typepaths) - var/query = getquery_systable_maketable(table_name, field_typepaths) - var/datum/db/query_response/sit_check = SSdatabase.create_query_sync(query) - if(sit_check.status != DB_QUERY_FINISHED) - issue_log += "Unable to create new table [table_name], error: '[sit_check.error]'" - return FALSE // OH SHIT OH FUCK - return TRUE - -/datum/db/adapter/brsql_adapter/proc/internal_record_table_in_sys(type_name, table_name, field_typepaths, id) - var/list/query_parameters = list() - var/query = getquery_systable_recordtable(type_name, table_name, field_typepaths, query_parameters, id) - var/datum/db/query_response/sit_check = SSdatabase.create_parametric_query_sync(query, query_parameters) - if(sit_check.status != DB_QUERY_FINISHED) - issue_log += "Unable to record meta for table [table_name], error: '[sit_check.error]'" - return FALSE // OH SHIT OH FUCK - return TRUE - -/datum/db/adapter/brsql_adapter/proc/internal_create_index(index_name, table_name, fields, unique, cluster) - var/query = getquery_sysindex_makeindex(index_name, table_name, fields, unique, cluster) +/datum/db/adapter/sql/brsql/proc/internal_create_index(index_name, table_name, fields, unique, cluster) + var/query = generate_make_index_sql(index_name, table_name, fields, unique, cluster) var/datum/db/query_response/sit_check = SSdatabase.create_query_sync(query) if(sit_check.status != DB_QUERY_FINISHED) issue_log += "Unable to create new index [index_name], error: '[sit_check.error]'" return FALSE // OH SHIT OH FUCK return TRUE -/datum/db/adapter/brsql_adapter/proc/internal_record_index_in_sys(index_name, table_name, fields, id) +/datum/db/adapter/sql/brsql/proc/internal_record_index_in_sys(index_name, table_name, fields, id) var/list/query_parameters = list() - var/query = getquery_sysindex_recordindex(index_name, table_name, fields, query_parameters, id) + var/query = generate_sysindex_record_sql(index_name, table_name, fields, query_parameters, id) var/datum/db/query_response/sit_check = SSdatabase.create_parametric_query_sync(query, query_parameters) if(sit_check.status != DB_QUERY_FINISHED) issue_log += "Unable to record meta for index [index_name], error: '[sit_check.error]'" @@ -227,78 +103,15 @@ return TRUE -/datum/db/adapter/brsql_adapter/proc/internal_drop_table(table_name) - var/query = getcommand_droptable(table_name) - var/datum/db/query_response/sit_check = SSdatabase.create_query_sync(query) - if(sit_check.status != DB_QUERY_FINISHED) - issue_log += "Unable to drop table [table_name], error: '[sit_check.error]'" - return FALSE // OH SHIT OH FUCK - return TRUE - -/datum/db/adapter/brsql_adapter/proc/internal_drop_index(index_name, table_name) - var/query = getcommand_dropindex(index_name, table_name) +/datum/db/adapter/sql/brsql/proc/internal_drop_index(index_name, table_name) + var/query = generate_drop_index_sql(index_name, table_name) var/datum/db/query_response/sit_check = SSdatabase.create_query_sync(query) if(sit_check.status != DB_QUERY_FINISHED) issue_log += "Unable to drop index [index_name], error: '[sit_check.error]'" return FALSE // OH SHIT OH FUCK return TRUE -/datum/db/adapter/brsql_adapter/proc/internal_drop_backup_table(table_name) - return internal_drop_table("[BRSQL_BACKUP_PREFIX][table_name]") - -// returns -1 if shit is fucked, otherwise returns count -/datum/db/adapter/brsql_adapter/proc/internal_table_count(table_name) - var/query = getquery_get_rowcount(table_name) - var/datum/db/query_response/rowcount = SSdatabase.create_query_sync(query) - if(rowcount.status != DB_QUERY_FINISHED) - issue_log += "Unable to get row count from table [table_name], error: '[rowcount.error]'" - return -1 // OH SHIT OH FUCK - return rowcount.results[1][BRSQL_COUNT_COLUMN_NAME] - -/datum/db/adapter/brsql_adapter/proc/internal_request_insert_allocation(table_name, size) - var/query = getquery_allocate_insert(table_name, size) - var/datum/db/query_response/first_id = SSdatabase.create_query_sync(query) - if(first_id.status != DB_QUERY_FINISHED) - issue_log += "Unable to allocate insert for [table_name], error: '[first_id.error]'" - return -1 // OH SHIT OH FUCK - var/value = first_id.results[1][BRSQL_COUNT_COLUMN_NAME] - if(!value) - return 1 - return value - -/datum/db/adapter/brsql_adapter/proc/internal_create_backup_table(table_name, field_typepaths) - var/query = getquery_systable_maketable("[BRSQL_BACKUP_PREFIX][table_name]", field_typepaths) - var/datum/db/query_response/sit_check = SSdatabase.create_query_sync(query) - if(sit_check.status != DB_QUERY_FINISHED) - issue_log += "Unable to create backup for table [table_name], error: '[sit_check.error]'" - return FALSE // OH SHIT OH FUCK - return TRUE - -/datum/db/adapter/brsql_adapter/proc/internal_migrate_table(table_name, list/field_typepaths_old) - var/list/fields = list(DB_DEFAULT_ID_FIELD) - for(var/field in field_typepaths_old) - fields += field - - var/query = getquery_insert_from_backup(table_name, fields) - var/datum/db/query_response/sit_check = SSdatabase.create_query_sync(query) - if(sit_check.status != DB_QUERY_FINISHED) - issue_log += "Unable to migrate table [table_name] from backup, error: '[sit_check.error]'" - return FALSE // OH SHIT OH FUCK - return TRUE - -/datum/db/adapter/brsql_adapter/proc/internal_migrate_to_backup(table_name, list/field_typepaths_old) - var/list/fields = list(DB_DEFAULT_ID_FIELD) - for(var/field in field_typepaths_old) - fields += field - - var/query = getquery_insert_into_backup(table_name, fields) - var/datum/db/query_response/sit_check = SSdatabase.create_query_sync(query) - if(sit_check.status != DB_QUERY_FINISHED) - issue_log += "Unable to migrate table [table_name] to backup, error: '[sit_check.error]'" - return FALSE // OH SHIT OH FUCK - return TRUE - -/datum/db/adapter/brsql_adapter/proc/internal_update_table(table_name, list/field_typepaths_new, list/field_typepaths_old) +/datum/db/adapter/sql/brsql/proc/internal_update_table(table_name, list/datum/db_field/field_typepaths_new, list/datum/db_field/field_typepaths_old) for(var/field in field_typepaths_old) if(!field_typepaths_new[field]) var/query = getquery_update_table_delete_column(table_name, field) @@ -323,47 +136,31 @@ return FALSE // OH SHIT OH FUCK return TRUE -// local stuff starts with "getquery_" -/datum/db/adapter/brsql_adapter/proc/getquery_systable_check() - return {" - CREATE TABLE IF NOT EXISTS `[connection.database]`.`[BRSQL_SYSTABLENAME]` (id INTEGER NOT NULL AUTO_INCREMENT PRIMARY KEY,type_name TEXT NOT NULL,table_name TEXT NOT NULL,fields_hash TEXT NOT NULL,fields_current TEXT NOT NULL) - "} - -/datum/db/adapter/brsql_adapter/proc/getquery_sysindex_check() - return {" - CREATE TABLE IF NOT EXISTS `[connection.database]`.`[BRSQL_SYSINDEXNAME]` (id INTEGER NOT NULL AUTO_INCREMENT PRIMARY KEY,index_name TEXT NOT NULL,table_name TEXT NOT NULL,fields_hash TEXT NOT NULL,fields_current TEXT NOT NULL) - "} - -/datum/db/adapter/brsql_adapter/proc/getquery_systable_gettable(table_name, list/qpar) - qpar.Add("[table_name]") - return {" - SELECT id, type_name, table_name, fields_hash, fields_current FROM `[connection.database]`.`[BRSQL_SYSTABLENAME]` WHERE table_name = ? - "} - -/datum/db/adapter/brsql_adapter/proc/getquery_sysindex_getindex(index_name, table_name, list/qpar) - qpar.Add("[index_name]") - qpar.Add("[table_name]") - return {" - SELECT id, index_name, table_name, fields_hash, fields_current FROM `[connection.database]`.`[BRSQL_SYSINDEXNAME]` WHERE index_name = ? AND table_name = ? - "} +/datum/db/adapter/sql/brsql/get_table_metadata_fields_sql() + return "id BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY" +/datum/db/adapter/sql/brsql/get_systable_fields_sql() + return "id INTEGER NOT NULL AUTO_INCREMENT PRIMARY KEY,type_name TEXT NOT NULL,table_name TEXT NOT NULL,fields_hash TEXT NOT NULL,fields_current TEXT NOT NULL" -/datum/db/adapter/brsql_adapter/proc/getquery_get_rowcount(table_name) +/// Generate SQL code to create the system index table if it does not exist +/datum/db/adapter/sql/brsql/proc/generate_sysindex_check_sql() return {" - SELECT count(1) as [BRSQL_COUNT_COLUMN_NAME] FROM `[connection.database]`.`[table_name]` + CREATE TABLE IF NOT EXISTS `[connection.database]`.`[sql_constants::SYSINDEXNAME]` (id INTEGER NOT NULL AUTO_INCREMENT PRIMARY KEY,index_name TEXT NOT NULL,table_name TEXT NOT NULL,fields_hash TEXT NOT NULL,fields_current TEXT NOT NULL) "} -/datum/db/adapter/brsql_adapter/proc/getcommand_droptable(table_name) +/datum/db/adapter/sql/brsql/proc/generate_get_index_sql(index_name, table_name, list/query_parameters) + query_parameters.Add("[index_name]") + query_parameters.Add("[table_name]") return {" - DROP TABLE IF EXISTS `[connection.database]`.`[table_name]` + SELECT id, index_name, table_name, fields_hash, fields_current FROM `[connection.database]`.`[sql_constants::SYSINDEXNAME]` WHERE index_name = ? AND table_name = ? "} -/datum/db/adapter/brsql_adapter/proc/getcommand_dropindex(index_name, table_name) +/datum/db/adapter/sql/brsql/proc/generate_drop_index_sql(index_name, table_name) return {" DROP INDEX IF EXISTS `[index_name]` on `[connection.database]`.`[table_name]` "} -/datum/db/adapter/brsql_adapter/proc/getquery_sysindex_makeindex(index_name, table_name, fields, unique, cluster) +/datum/db/adapter/sql/brsql/proc/generate_make_index_sql(index_name, table_name, fields, unique, cluster) var/unique_text = unique?"UNIQUE":"" var/field_text = jointext(fields,",") return {" @@ -372,78 +169,23 @@ ); "} -/datum/db/adapter/brsql_adapter/proc/getquery_systable_maketable(table_name, field_typepaths) - return {" - CREATE TABLE `[connection.database]`.`[table_name]` ( - id BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY, [fields2text(field_typepaths)] - ); - "} - -/datum/db/adapter/brsql_adapter/proc/getquery_systable_recordtable(type_name, table_name, field_typepaths, list/qpar, id = null) - var/field_text = fields2savetext(field_typepaths) - var/new_hash = sha1(field_text) - qpar.Add("[type_name]") - qpar.Add("[table_name]") - qpar.Add("[new_hash]") - qpar.Add("[field_text]") - if(!id) - return {" - INSERT INTO `[connection.database]`.`[BRSQL_SYSTABLENAME]` (type_name, table_name, fields_hash, fields_current) VALUES (?, ?, ?, ?); - "} - - return {" - UPDATE `[connection.database]`.`[BRSQL_SYSTABLENAME]` SET type_name = ?, table_name = ?, fields_hash = ?, fields_current= ? WHERE id=[text2num(id)]; - "} - -/datum/db/adapter/brsql_adapter/proc/getquery_sysindex_recordindex(index_name, table_name, fields, list/qpar, id = null) +/datum/db/adapter/sql/brsql/proc/generate_sysindex_record_sql(index_name, table_name, fields, list/query_parameters, id = null) var/field_text = jointext(fields, ",") var/new_hash = sha1(field_text) - qpar.Add("[index_name]") - qpar.Add("[table_name]") - qpar.Add("[new_hash]") - qpar.Add("[field_text]") + query_parameters.Add("[index_name]") + query_parameters.Add("[table_name]") + query_parameters.Add("[new_hash]") + query_parameters.Add("[field_text]") if(!id) return {" - INSERT INTO `[connection.database]`.`[BRSQL_SYSINDEXNAME]` (index_name, table_name, fields_hash, fields_current) VALUES (?, ?, ?, ?); + INSERT INTO `[connection.database]`.`[sql_constants::SYSINDEXNAME]` (index_name, table_name, fields_hash, fields_current) VALUES (?, ?, ?, ?); "} return {" - UPDATE `[connection.database]`.`[BRSQL_SYSINDEXNAME]` SET index_name = ?, table_name = ?, fields_hash = ?, fields_current= ? WHERE id=[text2num(id)]; + UPDATE `[connection.database]`.`[sql_constants::SYSINDEXNAME]` SET index_name = ?, table_name = ?, fields_hash = ?, fields_current= ? WHERE id=[text2num(id)]; "} -/datum/db/adapter/brsql_adapter/proc/getquery_insert_into_backup(table_name, list/fields) - var/field_text = jointext(fields, ", ") - return {" - INSERT INTO `[connection.database]`.`[BRSQL_BACKUP_PREFIX][table_name]` ([field_text]) - SELECT [field_text] FROM `[connection.database]`.`[table_name]` - "} - -/datum/db/adapter/brsql_adapter/proc/getquery_insert_from_backup(table_name, list/fields) - var/field_text = jointext(fields, ", ") - return {" - INSERT INTO `[connection.database]`.`[table_name]` ([field_text]) - SELECT [field_text] FROM `[connection.database]`.`[BRSQL_BACKUP_PREFIX][table_name]` - "} - - -/datum/db/adapter/brsql_adapter/proc/getquery_select_table(table_name, list/ids, list/fields) - var/id_text = "" - var/first = TRUE - for(var/id in ids) - if(!first) - id_text+="," - first = FALSE - id_text += "[text2num(id)]" // against some pesky injections - return {" - SELECT [fields?(""+jointext(fields,",")+""):"*"] FROM `[connection.database]`.`[table_name]` WHERE id in ([id_text]) - "} - -/datum/db/adapter/brsql_adapter/proc/getquery_filter_table(table_name, datum/db/filter, list/parameters, list/fields) - return {" - SELECT [fields?(""+jointext(fields,",")+""):"*"] FROM `[connection.database]`.`[table_name]` WHERE [get_filter(filter, null, parameters)] - "} - -/datum/db/adapter/brsql_adapter/proc/getquery_insert_table(table_name, list/values, list/parameters) +/datum/db/adapter/sql/brsql/proc/generate_insert_table_sql(table_name, list/values, list/query_parameters) var/calltext = "" var/insert_items = "" var/first = TRUE @@ -460,7 +202,7 @@ if(fields[field] == null) local_text += "NULL" else - parameters.Add("[fields[field]]") + query_parameters.Add("[fields[field]]") local_text += " ? " if(first) if(!local_first) @@ -474,7 +216,7 @@ INSERT INTO `[connection.database]`.`[table_name]` ([insert_items]) VALUES [calltext]; "} -/datum/db/adapter/brsql_adapter/proc/getquery_update_row(table_name, list/values, list/parameters) +/datum/db/adapter/sql/brsql/proc/getquery_update_row(table_name, list/values, list/query_parameters) var/calltext = "" var/first = TRUE var/id = 0 @@ -488,7 +230,7 @@ continue if(values[field]) calltext+="[esfield]=?" - parameters.Add("[values[field]]") + query_parameters.Add("[values[field]]") else calltext+="[esfield]=NULL" first = FALSE @@ -499,7 +241,7 @@ UPDATE `[connection.database]`.`[table_name]` SET [calltext] WHERE id = [id]; "} -/datum/db/adapter/brsql_adapter/proc/getquery_update_table(table_name, list/values, list/parameters) +/datum/db/adapter/sql/brsql/proc/getquery_update_table(table_name, list/values, list/query_parameters) var/calltext = "" var/update_items = "" var/first = TRUE @@ -519,7 +261,7 @@ local_text+="," if(fields[field] != null) local_text += "? as [esfield]" - parameters.Add(fields[field]) + query_parameters.Add(fields[field]) else local_text += "null as [esfield]" if(first && !is_id) @@ -537,33 +279,17 @@ UPDATE `[connection.database]`.`[table_name]` JOIN (WITH `__prep_update` AS ( [calltext] ) SELECT * FROM `__prep_update`) subquery ON `[table_name]`.id = subquery.id SET [update_items] "} -/datum/db/adapter/brsql_adapter/proc/getquery_delete_table(table_name, list/ids) - var/idtext = "" - var/first = TRUE - for(var/id in ids) - if(!first) - idtext+="," - idtext += "[text2num(id)]" - first = FALSE - return {" - DELETE FROM `[connection.database]`.`[table_name]` WHERE id IN ([idtext]); - "} - -/datum/db/adapter/brsql_adapter/proc/getquery_allocate_insert(table_name, size) - return {" - SELECT max(id) + 1 as total FROM `[connection.database]`.`[table_name]` - "} -/datum/db/adapter/brsql_adapter/proc/getquery_update_table_add_column(table_name, datum/db_field/column) +/datum/db/adapter/sql/brsql/proc/getquery_update_table_add_column(table_name, datum/db_field/column) return "ALTER TABLE `[connection.database]`.`[table_name]` ADD COLUMN `[column::name]` [fieldtype2text(column::field_type)];" -/datum/db/adapter/brsql_adapter/proc/getquery_update_table_change_column(table_name, datum/db_field/column) +/datum/db/adapter/sql/brsql/proc/getquery_update_table_change_column(table_name, datum/db_field/column) return "ALTER TABLE `[connection.database]`.`[table_name]` MODIFY COLUMN `[column::name]` [fieldtype2text(column::field_type)];" -/datum/db/adapter/brsql_adapter/proc/getquery_update_table_delete_column(table_name, column_name) +/datum/db/adapter/sql/brsql/proc/getquery_update_table_delete_column(table_name, column_name) return "ALTER TABLE `[connection.database]`.`[table_name]` DROP COLUMN `[column_name]`;" -/datum/db/adapter/brsql_adapter/proc/fieldtype2text(typeid) +/datum/db/adapter/sql/brsql/fieldtype2text(typeid) switch(typeid) if(DB_FIELDTYPE_INT) return "INT" @@ -589,210 +315,5 @@ return "DECIMAL(18,5)" return FALSE -/datum/db/adapter/brsql_adapter/proc/fields2text(list/datum/db_field/field_typepaths) - var/list/result = list() - for(var/field in field_typepaths) - var/datum/db_field/field_typepath = field_typepaths[field] - result += "[field_typepath::name] [fieldtype2text(field_typepath::field_type)]" - return jointext(result, ",") - -/datum/db/adapter/brsql_adapter/proc/fields2savetext(list/datum/db_field/field_typepaths) - var/list/result = list() - for(var/field in field_typepaths) - var/datum/db_field/field_typepath = field_typepaths[field] - result += "[field_typepath::name]:[field_typepath::field_type::type_id]" - return jointext(result, ",") - -#define FIELD_NAME_IDX 1 -#define FIELD_TYPE_ID_IDX 2 -/datum/db/adapter/brsql_adapter/proc/savetext2fields(text) - var/list/result = list() - var/list/fields_as_savetext = splittext(text, ",") - for(var/field_as_savetext in fields_as_savetext) - var/list/field_information = splittext(field_as_savetext, ":") - var/field_type_id = text2num(field_information[FIELD_TYPE_ID_IDX]) - result[field_information[FIELD_NAME_IDX]] = GLOB.db_field_types[field_type_id] - return result -#undef FIELD_NAME_IDX -#undef FIELD_TYPE_ID_IDX - -/datum/db/adapter/brsql_adapter/prepare_view(datum/entity_view_meta/view) - var/list/datum/entity_meta/meta_to_load = list(BRSQL_ROOT_NAME = view.root_entity_meta) - var/list/meta_to_table = list(BRSQL_ROOT_NAME = BRSQL_ROOT_ALIAS) - var/list/datum/db/filter/join_conditions = list() - var/list/field_alias = list() - var/list/shared_options = list() - shared_options["table_alias_id"] = 1 - for(var/field in view.fields) - var/field_path = view.fields[field] - if(!field_path) - field_path = field - internal_parse_column(field, field_path, view, shared_options, meta_to_load, meta_to_table, join_conditions, field_alias) - - for(var/field in view.group_by) - var/field_path = view.fields[field] - if(!field_path) - field_path = field - internal_parse_column(field, field_path, view, shared_options, meta_to_load, meta_to_table, join_conditions, field_alias) - - if(view.root_filter) - var/list/filter_columns = view.root_filter.get_columns() - for(var/field in filter_columns) - internal_parse_column(field, field, view, shared_options, meta_to_load, meta_to_table, join_conditions, field_alias) - - internal_generate_view_query(view, shared_options, meta_to_load, meta_to_table, join_conditions, field_alias) - -/datum/db/adapter/brsql_adapter/proc/internal_proc_to_text(datum/db/native_function/native_function, list/field_alias, list/parameters) - switch(native_function.type) - if(/datum/db/native_function/case) - var/datum/db/native_function/case/case_f = native_function - var/result_true = case_f.result_true - var/result_false = case_f.result_false - var/condition_text = get_filter(case_f.condition, field_alias, parameters) - var/true_text - if(result_true) - var/datum/db/native_function/native_true = result_true - if(istype(native_true)) - true_text = internal_proc_to_text(native_true, field_alias, parameters) - else - var/field_cast = "[result_true]" - if(field_alias && field_alias[field_cast]) - field_cast = field_alias[field_cast] - true_text = field_cast - var/false_text - if(result_false) - var/datum/db/native_function/native_false = result_false - if(istype(native_false)) - false_text = internal_proc_to_text(native_false, field_alias, parameters) - else - var/field_cast = "[result_false]" - if(field_alias && field_alias[field_cast]) - field_cast = field_alias[field_cast] - false_text = field_cast - if(false_text) - false_text = "ELSE ([false_text]) " - return "CASE WHEN [condition_text] THEN ([true_text]) [false_text] END" - else - return native_function.default_to_string(field_alias, parameters) - -/datum/db/adapter/brsql_adapter/proc/internal_generate_view_query(datum/entity_view_meta/view, list/shared_options, list/datum/entity_meta/meta_to_load, list/meta_to_table, list/datum/db/filter/join_conditions, list/field_alias) - var/list/pre_pflds = list() - var/query_text = "SELECT " - for(var/fld in view.fields) - var/field = field_alias[fld] - if(istype(field, /datum/db/native_function)) - var/datum/db/native_function/native_function = field - field = internal_proc_to_text(native_function, field_alias, pre_pflds) - query_text += "[field] as `[fld]`, " - query_text += "1 as is_view " - query_text += "FROM `[connection.database]`.`[meta_to_load[BRSQL_ROOT_NAME].table_name]` as `[meta_to_table[BRSQL_ROOT_NAME]]` " - var/join_text = "" - for(var/mtt in meta_to_table) - if(mtt == BRSQL_ROOT_NAME) - continue - var/alias_t = meta_to_table[mtt] - var/name_t = meta_to_load[mtt].table_name - var/join_c = get_filter(join_conditions[alias_t], null, pre_pflds) - join_text += "LEFT JOIN " - join_text += "`[connection.database]`.`[name_t]` as `[alias_t]` on [join_c] " - - query_text += join_text - - query_text += "WHERE " - - var/query_part_1 = query_text - - var/list/post_pflds = list() - - query_text = "" - - if(view.root_filter) - var/filter_text = get_filter(view.root_filter, field_alias, post_pflds) - query_text += "AND ([filter_text]) " - - var/order_length = length(view.order_by) - - if(order_length) - var/order_text = "ORDER BY " - var/index_order = 1 - for(var/order_field in view.order_by) - var/order_d = view.order_by[order_field] - order_text += "[field_alias[order_field]] " - if(order_d == DB_ORDER_BY_ASC) - order_text += "ASC" - if(order_d == DB_ORDER_BY_DESC) - order_text += "DESC" - if(index_order != order_length) - order_text += "," - index_order++ - query_text += order_text - - var/group_length = length(view.group_by) - - if(group_length) - var/group_text = "GROUP BY " - var/index_order = 1 - for(var/fld in view.group_by) - var/field = field_alias[fld] - group_text += "[field]" - if(index_order != group_length) - group_text += "," - query_text += group_text + " " - - var/query_part_2 = query_text - var/key = "v_[view.type]" - cached_queries[key] = new /datum/db/brsql_cached_query(src, query_part_1, query_part_2, field_alias, pre_pflds, post_pflds) - - -/datum/db/adapter/brsql_adapter/proc/internal_parse_column(field, field_value, datum/entity_view_meta/view, list/shared_options, list/datum/entity_meta/meta_to_load, list/meta_to_table, list/datum/db/filter/join_conditions, list/field_alias) - var/datum/db/native_function/native_function = field_value - // this is a function? - if(istype(native_function)) - var/list/field_list = native_function.get_columns() - - for(var/sub_field in field_list) - internal_parse_column(sub_field, sub_field, view, shared_options, meta_to_load, meta_to_table, join_conditions, field_alias) - if(field) - field_alias[field] = native_function - - return - // no, this is a normal field - // already parsed? - if(field && field_alias[field]) - return - - var/datum/entity_meta/current_root = view.root_entity_meta - var/current_field = field_value - var/current_path = "" - var/current_alias = meta_to_table[BRSQL_ROOT_NAME] - var/link_loc = findtextEx(current_field, ".") - while(link_loc) - var/table_link = COPY_FROM_START(current_field, link_loc) - if(!table_link) - break - if(current_path) - current_path += "." + table_link - else - current_path = table_link - current_field = COPY_AFTER_FOUND(current_field, link_loc) - link_loc = findtextEx(current_field, ".") - if(!meta_to_table[current_path]) - var/step_child = "T_[shared_options["table_alias_id"]]" - meta_to_table[current_path] = step_child - shared_options["table_alias_id"]++ - var/datum/entity_link/next_link = current_root.outbound_links[table_link] - if(next_link) - meta_to_load[current_path] = next_link.parent_meta - join_conditions[step_child] = next_link.get_filter(step_child, current_alias) - else - next_link = current_root.inbound_links[table_link] - if(!next_link) - return FALSE // failed to initialize view - meta_to_load[current_path] = next_link.child_meta - join_conditions[step_child] = next_link.get_filter(current_alias, step_child) - - current_root = meta_to_load[current_path] - current_alias = meta_to_table[current_path] - - if(field) - field_alias[field] = "`[current_alias]`.`[current_field]`" +/datum/db/adapter/sql/brsql/table_name_to_sql(table_name) + return "`[connection.database]`.`[table_name]`" diff --git a/code/datums/_ndatabase/code/brsql_persistent_connection.dm b/code/datums/_ndatabase/code/brsql_persistent_connection.dm index 524c99eecc1f..9eea95d57bfd 100644 --- a/code/datums/_ndatabase/code/brsql_persistent_connection.dm +++ b/code/datums/_ndatabase/code/brsql_persistent_connection.dm @@ -102,6 +102,6 @@ return null /datum/db/connection/brsql_connection/get_adapter() - var/datum/db/adapter/brsql_adapter/adapter = new() + var/datum/db/adapter/sql/brsql/adapter = new() adapter.connection = src return adapter diff --git a/code/datums/_ndatabase/code/entity/entity_meta.dm b/code/datums/_ndatabase/code/entity/entity_meta.dm index bbc5397ae9a3..60cc9ca18875 100644 --- a/code/datums/_ndatabase/code/entity/entity_meta.dm +++ b/code/datums/_ndatabase/code/entity/entity_meta.dm @@ -34,6 +34,7 @@ var/list/datum/entity/managed var/list/datum/entity/key_managed + /// List of valid entities that exist in the table var/list/datum/entity/to_read var/list/datum/entity/to_insert var/list/datum/entity/to_update diff --git a/code/datums/_ndatabase/code/entity/link.dm b/code/datums/_ndatabase/code/entity/link.dm index 04872643a159..12b9db8062ca 100644 --- a/code/datums/_ndatabase/code/entity/link.dm +++ b/code/datums/_ndatabase/code/entity/link.dm @@ -29,8 +29,9 @@ /// Entity that is referencing a different entity var/child_entity /// Column on child entity to reference parent entity. - /// A typepath to the db_field datum that represents the column - var/child_foreign_key + /// A typepath to the db_field datum that represents the column that is casted for access to + /// values from the field's type definition + var/datum/db_field/child_foreign_key var/datum/entity_meta/parent_meta var/datum/entity_meta/child_meta diff --git a/code/datums/_ndatabase/code/native_adapter.dm b/code/datums/_ndatabase/code/native_adapter.dm index ab53b4df093f..d0d87fff76e9 100644 --- a/code/datums/_ndatabase/code/native_adapter.dm +++ b/code/datums/_ndatabase/code/native_adapter.dm @@ -19,311 +19,52 @@ // 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. -#define NATIVE_SYSTABLENAME "__entity_master" -#define NATIVE_BACKUP_PREFIX "__backup_" -#define NATIVE_COUNT_COLUMN_NAME "total" -#define NATIVE_ROOT_NAME "__root" -#define NATIVE_ROOT_ALIAS "T_root" -/datum/db/adapter/native_adapter - var/list/issue_log +/datum/db/adapter/sql/native var/datum/db/connection/native/connection - var/list/datum/db/native_cached_query/cached_queries -/datum/db/adapter/native_adapter/New() - issue_log = list() - cached_queries = list() - -// this implementation is pretty janky, our goal here to create query we can work with, not cover every possible case. -// don't make same mistake I did. 20% jank code that is usable now is better than 0% jank code that covers every case that won't even happen and is written in a year -/datum/db/native_cached_query - var/datum/db/adapter/native_adapter/adapter - // text of the query before we put filter - var/pre_filter - // text of the query after we put filter - var/post_filter - // casts, because our query is already mapped to internal `T_n` structure, while whatever the hell we get is not - var/list/casts = list() - // list of pre pflds - var/list/pre_pflds - // list of post pflds - var/list/post_pflds - -/datum/db/native_cached_query/New(_adapter, _pre_filter, _post_filter, _casts, _pre_pflds, _post_pflds) - adapter = _adapter - pre_filter = _pre_filter - post_filter = _post_filter - casts = _casts - pre_pflds = _pre_pflds - post_pflds = _post_pflds - -// maybe pflds can be changed to named parameters? but that might take time. The contract is set, so changes here shouldn't affect your code besides performance -/datum/db/native_cached_query/proc/spawn_query(datum/db/filter/F, list/pflds) - for(var/i in pre_pflds) - pflds.Add(i) - var/query = "[pre_filter] [adapter.get_filter(F, casts, pflds)] [post_filter]" - for(var/i in post_pflds) - pflds.Add(i) - return query - -/datum/db/adapter/native_adapter/sync_table_meta() - var/query = getquery_systable_check() +/datum/db/adapter/sql/native/sync_table_meta() + var/query = generate_systable_check_sql() var/datum/db/query_response/sys_table = SSdatabase.create_query_sync(query) if(sys_table.status != DB_QUERY_FINISHED) issue_log += "Unable to create or access system table, error: '[sys_table.error]'" return FALSE // OH SHIT OH FUCK return TRUE - -/datum/db/adapter/native_adapter/read_table(table_name, list/ids, datum/callback/CB, sync = FALSE) - var/query_gettable = getquery_select_table(table_name, ids) - if(sync) - SSdatabase.create_query_sync(query_gettable, CB) - else - SSdatabase.create_query(query_gettable, CB) - -/datum/db/adapter/native_adapter/update_table(table_name, list/values, datum/callback/CB, sync = FALSE) +/datum/db/adapter/sql/native/update_table(table_name, list/values, datum/callback/on_update_table_callback, sync = FALSE) set waitfor = FALSE for(var/list/vals in values) - var/list/qpars = list() - var/query_updaterow = getquery_update_row(table_name, vals, qpars) - SSdatabase.create_parametric_query_sync(query_updaterow, qpars, CB) + var/list/query_parameters = list() + var/query_updaterow = getquery_update_row(table_name, vals, query_parameters) + SSdatabase.create_parametric_query_sync(query_updaterow, query_parameters, on_update_table_callback) + +/datum/db/adapter/sql/native/insert_table(table_name, list/values, datum/callback/on_insert_table_callback, sync = FALSE) + set waitfor = FALSE -/datum/db/adapter/native_adapter/insert_table(table_name, list/values, datum/callback/CB, sync = FALSE) - set waitfor = 0 var/length = length(values) var/startid = internal_request_insert_allocation(table_name, length) - var/list/qpars = list() - var/query_inserttable = getquery_insert_table(table_name, values, startid, qpars) - if(!CB.arguments) - CB.arguments = list() - CB.arguments.Add(startid) - if(sync) - SSdatabase.create_parametric_query_sync(query_inserttable, qpars, CB) - else - SSdatabase.create_parametric_query(query_inserttable, qpars, CB) - -/datum/db/adapter/native_adapter/delete_table(table_name, list/ids, datum/callback/CB, sync = FALSE) - var/query_deletetable = getquery_delete_table(table_name, ids) - if(sync) - SSdatabase.create_query_sync(query_deletetable, CB) - else - SSdatabase.create_query(query_deletetable, CB) - -/datum/db/adapter/native_adapter/read_filter(table_name, datum/db/filter, datum/callback/CB, sync = FALSE) - var/list/qpars = list() - var/query_gettable = getquery_filter_table(table_name, filter, qpars) + var/list/query_parameters = list() + var/query_inserttable = generate_insert_table_sql(table_name, values, startid, query_parameters) + if(!on_insert_table_callback.arguments) + on_insert_table_callback.arguments = list() + on_insert_table_callback.arguments.Add(startid) if(sync) - SSdatabase.create_parametric_query_sync(query_gettable, qpars, CB) + SSdatabase.create_parametric_query_sync(query_inserttable, query_parameters, on_insert_table_callback) else - SSdatabase.create_parametric_query(query_gettable, qpars, CB) - -/datum/db/adapter/native_adapter/read_view(datum/entity_view_meta/view, datum/db/filter/filter, datum/callback/CB, sync=FALSE) - var/v_key = "v_[view.type]" - var/list/qpars = list() - var/datum/db/native_cached_query/cached_view = cached_queries[v_key] - if(!istype(cached_view)) - return null - var/query_getview = cached_view.spawn_query(filter, qpars) - if(sync) - SSdatabase.create_parametric_query_sync(query_getview, qpars, CB) - else - SSdatabase.create_parametric_query(query_getview, qpars, CB) - -/datum/db/adapter/native_adapter/sync_table(type_name, table_name, list/field_typepaths) - var/list/qpars = list() - var/query_gettable = getquery_systable_gettable(table_name, qpars) - var/datum/db/query_response/table_meta = SSdatabase.create_parametric_query_sync(query_gettable, qpars) - if(table_meta.status != DB_QUERY_FINISHED) - issue_log += "Unable to access system table, error: '[table_meta.error]'" - return FALSE // OH SHIT OH FUCK - if(!length(table_meta.results)) // Table doesn't exist - return internal_create_table(table_name, field_typepaths) && internal_record_table_in_sys(type_name, table_name, field_typepaths) - - var/id = table_meta.results[1][DB_DEFAULT_ID_FIELD] - var/old_fields = savetext2fields(table_meta.results[1]["fields_current"]) - var/old_hash = table_meta.results[1]["fields_hash"] - var/field_text = fields2savetext(field_typepaths) - var/new_hash = sha1(field_text) - - if(old_hash == new_hash) - return TRUE // no action required - - var/tablecount = internal_table_count(table_name) - // check if we have any records - if(tablecount == 0) - // just MURDER IT - return internal_drop_table(table_name) && internal_create_table(table_name, field_typepaths) && internal_record_table_in_sys(type_name, table_name, field_typepaths, id) - - return internal_drop_backup_table(table_name) && internal_create_backup_table(table_name, old_fields) && internal_migrate_to_backup(table_name, old_fields) && \ - internal_drop_table(table_name) && internal_create_table(table_name, field_typepaths) && internal_migrate_table(table_name, old_fields) && internal_record_table_in_sys(type_name, table_name, field_typepaths, id) - - - -/datum/db/adapter/native_adapter/proc/internal_create_table(table_name, field_typepaths) - var/query = getquery_systable_maketable(table_name, field_typepaths) - var/datum/db/query_response/sit_check = SSdatabase.create_query_sync(query) - if(sit_check.status != DB_QUERY_FINISHED) - issue_log += "Unable to create new table [table_name], error: '[sit_check.error]'" - return FALSE // OH SHIT OH FUCK - return TRUE - -/datum/db/adapter/native_adapter/proc/internal_record_table_in_sys(type_name, table_name, field_typepaths, id) - var/list/qpars = list() - var/query = getquery_systable_recordtable(type_name, table_name, field_typepaths, qpars, id) - var/datum/db/query_response/sit_check = SSdatabase.create_parametric_query_sync(query, qpars) - if(sit_check.status != DB_QUERY_FINISHED) - issue_log += "Unable to record meta for table [table_name], error: '[sit_check.error]'" - return FALSE // OH SHIT OH FUCK - return TRUE - -/datum/db/adapter/native_adapter/proc/internal_drop_table(table_name) - var/query = getcommand_droptable(table_name) - var/datum/db/query_response/sit_check = SSdatabase.create_query_sync(query) - if(sit_check.status != DB_QUERY_FINISHED) - issue_log += "Unable to drop table [table_name], error: '[sit_check.error]'" - return FALSE // OH SHIT OH FUCK - return TRUE - -/datum/db/adapter/native_adapter/proc/internal_drop_backup_table(table_name) - var/query = getcommand_droptable("[NATIVE_BACKUP_PREFIX][table_name]") - var/datum/db/query_response/sit_check = SSdatabase.create_query_sync(query) - if(sit_check.status != DB_QUERY_FINISHED) - issue_log += "Unable to drop table [table_name], error: '[sit_check.error]'" - return FALSE // OH SHIT OH FUCK - return TRUE - -// returns -1 if shit is fucked, otherwise returns count -/datum/db/adapter/native_adapter/proc/internal_table_count(table_name) - var/query = getquery_get_rowcount(table_name) - var/datum/db/query_response/rowcount = SSdatabase.create_query_sync(query) - if(rowcount.status != DB_QUERY_FINISHED) - issue_log += "Unable to get row count from table [table_name], error: '[rowcount.error]'" - return -1 // OH SHIT OH FUCK - return rowcount.results[1][NATIVE_COUNT_COLUMN_NAME] - -/datum/db/adapter/native_adapter/proc/internal_request_insert_allocation(table_name, size) - var/query = getquery_allocate_insert(table_name, size) - var/datum/db/query_response/first_id = SSdatabase.create_query_sync(query) - if(first_id.status != DB_QUERY_FINISHED) - issue_log += "Unable to allocate insert for [table_name], error: '[first_id.error]'" - return -1 // OH SHIT OH FUCK - var/value = first_id.results[1][NATIVE_COUNT_COLUMN_NAME] - if(!value) - return 1 - return value - -/datum/db/adapter/native_adapter/proc/internal_create_backup_table(table_name, field_typepaths) - var/query = getquery_systable_maketable("[NATIVE_BACKUP_PREFIX][table_name]", field_typepaths) - var/datum/db/query_response/sit_check = SSdatabase.create_query_sync(query) - if(sit_check.status != DB_QUERY_FINISHED) - issue_log += "Unable to create backup for table [table_name], error: '[sit_check.error]'" - return FALSE // OH SHIT OH FUCK - return TRUE - -/datum/db/adapter/native_adapter/proc/internal_migrate_table(table_name, list/field_typepaths_old) - var/list/fields = list(DB_DEFAULT_ID_FIELD) - for(var/field in field_typepaths_old) - fields += field - - var/query = getquery_insert_from_backup(table_name, fields) - var/datum/db/query_response/sit_check = SSdatabase.create_query_sync(query) - if(sit_check.status != DB_QUERY_FINISHED) - issue_log += "Unable to migrate table [table_name] to backup, error: '[sit_check.error]'" - return FALSE // OH SHIT OH FUCK - return TRUE - -/datum/db/adapter/native_adapter/proc/internal_migrate_to_backup(table_name, list/field_typepaths_old) - var/list/fields = list(DB_DEFAULT_ID_FIELD) - for(var/field in field_typepaths_old) - fields += field - - var/query = getquery_insert_into_backup(table_name, fields) - var/datum/db/query_response/sit_check = SSdatabase.create_query_sync(query) - if(sit_check.status != DB_QUERY_FINISHED) - issue_log += "Unable to migrate table [table_name] to backup, error: '[sit_check.error]'" - return FALSE // OH SHIT OH FUCK - return TRUE - -// local stuff starts with "getquery_" -/datum/db/adapter/native_adapter/proc/getquery_systable_check() - return {" - CREATE TABLE IF NOT EXISTS [NATIVE_SYSTABLENAME] (id INTEGER PRIMARY KEY,type_name TEXT NOT NULL,table_name TEXT NOT NULL,fields_hash TEXT NOT NULL,fields_current TEXT NOT NULL) - "} - -/datum/db/adapter/native_adapter/proc/getquery_systable_gettable(table_name, list/qpar) - qpar.Add("[table_name]") - return {" - SELECT id, type_name, table_name, fields_hash, fields_current FROM [NATIVE_SYSTABLENAME] WHERE table_name = ? - "} - -/datum/db/adapter/native_adapter/proc/getquery_get_rowcount(table_name) - return {" - SELECT count(1) as [NATIVE_COUNT_COLUMN_NAME] FROM [table_name] - "} - -/datum/db/adapter/native_adapter/proc/getcommand_droptable(table_name) - return {" - DROP TABLE IF EXISTS [table_name] - "} - -/datum/db/adapter/native_adapter/proc/getquery_systable_maketable(table_name, field_typepaths) - return {" - CREATE TABLE [table_name] ( - id BIGINT NOT NULL PRIMARY KEY, [fields2text(field_typepaths)] - ); - "} - -/datum/db/adapter/native_adapter/proc/getquery_systable_recordtable(type_name, table_name, field_typepaths, list/qpar, id = null) - var/field_text = fields2savetext(field_typepaths) - var/new_hash = sha1(field_text) - qpar.Add("[type_name]") - qpar.Add("[table_name]") - qpar.Add("[new_hash]") - qpar.Add("[field_text]") - if(!id) - return {" - INSERT INTO [NATIVE_SYSTABLENAME] (type_name, table_name, fields_hash, fields_current) VALUES (?, ?, ?, ?); - "} - - return {" - UPDATE [NATIVE_SYSTABLENAME] SET type_name = ?, table_name = ?, fields_hash = ?, fields_current= ? WHERE id=[text2num(id)]; - "} + SSdatabase.create_parametric_query(query_inserttable, query_parameters, on_insert_table_callback) -/datum/db/adapter/native_adapter/proc/getquery_insert_into_backup(table_name, list/fields) - var/field_text = jointext(fields, ", ") - return {" - INSERT INTO [NATIVE_BACKUP_PREFIX][table_name] ([field_text]) - SELECT [field_text] FROM [table_name] - "} +/datum/db/adapter/sql/native/complete_sync(type_name, table_name, list/datum/db_field/field_typepaths, list/datum/db_field/old_field_typepaths, id) + return internal_drop_backup_table(table_name) && internal_create_backup_table(table_name, old_field_typepaths) && internal_migrate_to_backup(table_name, old_field_typepaths) && \ + internal_drop_table(table_name) && internal_create_table(table_name, field_typepaths) && internal_migrate_table(table_name, old_field_typepaths) && internal_record_table_in_sys(type_name, table_name, field_typepaths, id) -/datum/db/adapter/native_adapter/proc/getquery_insert_from_backup(table_name, list/fields) - var/field_text = jointext(fields, ", ") - return {" - INSERT INTO [table_name] ([field_text]) - SELECT [field_text] FROM [NATIVE_BACKUP_PREFIX][table_name] - "} +/datum/db/adapter/sql/native/get_table_metadata_fields_sql() + return "id BIGINT NOT NULL PRIMARY KEY" +/datum/db/adapter/sql/native/get_systable_fields_sql() + return "id INTEGER PRIMARY KEY,type_name TEXT NOT NULL,table_name TEXT NOT NULL,fields_hash TEXT NOT NULL,fields_current TEXT NOT NULL" -/datum/db/adapter/native_adapter/proc/getquery_select_table(table_name, list/ids, list/fields) - var/id_text = "" - var/first = TRUE - for(var/id in ids) - if(!first) - id_text+="," - first = FALSE - id_text += "[text2num(id)]" // against some pesky injections - return {" - SELECT [fields?(""+jointext(fields,",")+""):"*"] FROM [table_name] WHERE id in ([id_text]) - "} - -/datum/db/adapter/native_adapter/proc/getquery_filter_table(table_name, datum/db/filter, list/pflds, list/fields) - return {" - SELECT [fields?(""+jointext(fields,",")+""):"*"] FROM [table_name] WHERE [get_filter(filter, null, pflds)] - "} - -/datum/db/adapter/native_adapter/proc/getquery_insert_table(table_name, list/values, start_id, list/pflds) +/datum/db/adapter/sql/native/proc/generate_insert_table_sql(table_name, list/values, start_id, list/query_parameters) var/calltext = "" var/insert_items = "" var/id = text2num(start_id) @@ -341,7 +82,7 @@ if(fields[field] == null) local_text += "NULL" else - pflds.Add("[fields[field]]") + query_parameters.Add("[fields[field]]") local_text += " ? " if(first) if(!local_first) @@ -356,7 +97,7 @@ INSERT INTO [table_name] (id, [insert_items]) [calltext]; "} -/datum/db/adapter/native_adapter/proc/getquery_update_row(table_name, list/values, list/pflds) +/datum/db/adapter/sql/native/proc/getquery_update_row(table_name, list/values, list/query_parameters) var/calltext = "" var/first = TRUE var/id = 0 @@ -370,7 +111,7 @@ continue if(values[field]) calltext+="[esfield]=?" - pflds.Add("[values[field]]") + query_parameters.Add("[values[field]]") else calltext+="[esfield]=NULL" first = FALSE @@ -381,24 +122,7 @@ UPDATE [table_name] SET [calltext] WHERE id = [id]; "} -/datum/db/adapter/native_adapter/proc/getquery_delete_table(table_name, list/ids) - var/idtext = "" - var/first = TRUE - for(var/id in ids) - if(!first) - idtext+="," - idtext += "[text2num(id)]" - first = FALSE - return {" - DELETE FROM [table_name] WHERE id IN ([idtext]); - "} - -/datum/db/adapter/native_adapter/proc/getquery_allocate_insert(table_name, size) - return {" - SELECT max(id) + 1 as total FROM [table_name] - "} - -/datum/db/adapter/native_adapter/proc/fieldtype2text(typeid) +/datum/db/adapter/sql/native/fieldtype2text(typeid) switch(typeid) if(DB_FIELDTYPE_INT) return "INT" @@ -423,204 +147,3 @@ if(DB_FIELDTYPE_DECIMAL) return "NUMERIC" return FALSE - -/datum/db/adapter/native_adapter/proc/fields2text(list/L) - var/list/result = list() - for(var/item in L) - result += "[item] [fieldtype2text(L[item])]" - return jointext(result, ",") - -/datum/db/adapter/native_adapter/proc/fields2savetext(list/L) - var/list/result = list() - for(var/item in L) - result += "[item]:[L[item]]" - return jointext(result, ",") - -/datum/db/adapter/native_adapter/proc/savetext2fields(text) - var/list/result = list() - var/list/split1 = splittext(text, ",") - for(var/field in split1) - var/list/split2 = splittext(field, ":") - result[split2[1]] = text2num(split2[2]) - return result - -/datum/db/adapter/native_adapter/prepare_view(datum/entity_view_meta/view) - var/list/datum/entity_meta/meta_to_load = list(NATIVE_ROOT_NAME = view.root_entity_meta) - var/list/meta_to_table = list(NATIVE_ROOT_NAME = NATIVE_ROOT_ALIAS) - var/list/datum/db/filter/join_conditions = list() - var/list/field_alias = list() - var/list/shared_options = list() - shared_options["table_alias_id"] = 1 - for(var/field in view.fields) - var/field_path = view.fields[field] - if(!field_path) - field_path = field - internal_parse_column(field, field_path, view, shared_options, meta_to_load, meta_to_table, join_conditions, field_alias) - - for(var/field in view.group_by) - var/field_path = view.fields[field] - if(!field_path) - field_path = field - internal_parse_column(field, field_path, view, shared_options, meta_to_load, meta_to_table, join_conditions, field_alias) - - if(view.root_filter) - var/list/filter_columns = view.root_filter.get_columns() - for(var/field in filter_columns) - internal_parse_column(field, field, view, shared_options, meta_to_load, meta_to_table, join_conditions, field_alias) - - internal_generate_view_query(view, shared_options, meta_to_load, meta_to_table, join_conditions, field_alias) - -/datum/db/adapter/native_adapter/proc/internal_proc_to_text(datum/db/native_function/NF, list/field_alias, list/pflds) - switch(NF.type) - if(/datum/db/native_function/case) - var/datum/db/native_function/case/case_f = NF - var/result_true = case_f.result_true - var/result_false = case_f.result_false - var/condition_text = get_filter(case_f.condition, field_alias, pflds) - var/true_text - if(result_true) - var/datum/db/native_function/native_true = result_true - if(istype(native_true)) - true_text = internal_proc_to_text(native_true, field_alias, pflds) - else - var/field_cast = "[result_true]" - if(field_alias && field_alias[field_cast]) - field_cast = field_alias[field_cast] - true_text = field_cast - var/false_text - if(result_false) - var/datum/db/native_function/native_false = result_false - if(istype(native_false)) - false_text = internal_proc_to_text(native_false, field_alias, pflds) - else - var/field_cast = "[result_false]" - if(field_alias && field_alias[field_cast]) - field_cast = field_alias[field_cast] - false_text = field_cast - if(false_text) - false_text = "ELSE ([false_text]) " - return "CASE WHEN [condition_text] THEN ([true_text]) [false_text] END" - else - return NF.default_to_string(field_alias, pflds) - -/datum/db/adapter/native_adapter/proc/internal_generate_view_query(datum/entity_view_meta/view, list/shared_options, list/datum/entity_meta/meta_to_load, list/meta_to_table, list/datum/db/filter/join_conditions, list/field_alias) - var/list/pre_pflds = list() - var/query_text = "SELECT " - for(var/fld in view.fields) - var/field = field_alias[fld] - var/datum/db/native_function/NF = field - // this is a function? - if(istype(NF)) - field = internal_proc_to_text(NF, field_alias, pre_pflds) - query_text += "[field] as `[fld]`, " - query_text += "1 as `is_view` " - query_text += "FROM `[meta_to_load[NATIVE_ROOT_NAME].table_name]` as `[meta_to_table[NATIVE_ROOT_NAME]]` " - var/join_text = "" - for(var/mtt in meta_to_table) - if(mtt == NATIVE_ROOT_NAME) - continue - var/alias_t = meta_to_table[mtt] - var/name_t = meta_to_load[mtt].table_name - var/join_c = get_filter(join_conditions[alias_t], null, pre_pflds) - join_text += "LEFT JOIN `[name_t]` as `[alias_t]` on [join_c] " - - query_text += join_text - - query_text += "WHERE " - - var/query_part_1 = query_text - - var/list/post_pflds = list() - - query_text = "" - - if(view.root_filter) - var/filter_text = get_filter(view.root_filter, field_alias, post_pflds) - query_text += "AND ([filter_text]) " - - var/order_length = length(view.order_by) - - if(order_length) - var/order_text = "ORDER BY " - var/index_order = 1 - for(var/order_field in view.order_by) - var/order_d = view.order_by[order_field] - order_text += "[field_alias[order_field]] " - if(order_d == DB_ORDER_BY_ASC) - order_text += "ASC" - if(order_d == DB_ORDER_BY_DESC) - order_text += "DESC" - if(index_order != order_length) - order_text += "," - index_order++ - query_text += order_text - - var/group_length = length(view.group_by) - - if(group_length) - var/group_text = "GROUP BY " - var/index_order = 1 - for(var/fld in view.group_by) - var/field = field_alias[fld] - group_text += "[field]" - if(index_order != group_length) - group_text += "," - query_text += group_text + " " - - var/query_part_2 = query_text - var/key = "v_[view.type]" - cached_queries[key] = new /datum/db/native_cached_query(src, query_part_1, query_part_2, field_alias, pre_pflds, post_pflds) - - -/datum/db/adapter/native_adapter/proc/internal_parse_column(field, field_value, datum/entity_view_meta/view, list/shared_options, list/datum/entity_meta/meta_to_load, list/meta_to_table, list/datum/db/filter/join_conditions, list/field_alias) - var/datum/db/native_function/NF = field_value - // this is a function? - if(istype(NF)) - var/list/field_list = NF.get_columns() - - for(var/sub_field in field_list) - internal_parse_column(sub_field, sub_field, view, shared_options, meta_to_load, meta_to_table, join_conditions, field_alias) - if(field) - field_alias[field] = NF - - return - // no, this is a normal field - // already parsed? - if(field && field_alias[field]) - return - - var/datum/entity_meta/current_root = view.root_entity_meta - var/current_field = field_value - var/current_path = "" - var/current_alias = meta_to_table[NATIVE_ROOT_NAME] - var/link_loc = findtextEx(current_field, ".") - while(link_loc) - var/table_link = COPY_FROM_START(current_field, link_loc) - if(!table_link) - break - if(current_path) - current_path += "." + table_link - else - current_path = table_link - current_field = COPY_AFTER_FOUND(current_field, link_loc) - link_loc = findtextEx(current_field, ".") - if(!meta_to_table[current_path]) - var/step_child = "T_[shared_options["table_alias_id"]]" - meta_to_table[current_path] = step_child - shared_options["table_alias_id"]++ - var/datum/entity_link/next_link = current_root.outbound_links[table_link] - if(next_link) - meta_to_load[current_path] = next_link.parent_meta - join_conditions[step_child] = next_link.get_filter(step_child, current_alias) - else - next_link = current_root.inbound_links[table_link] - if(!next_link) - return FALSE // failed to initialize view - meta_to_load[current_path] = next_link.child_meta - join_conditions[step_child] = next_link.get_filter(current_alias, step_child) - - current_root = meta_to_load[current_path] - current_alias = meta_to_table[current_path] - - if(field) - field_alias[field] = "`[current_alias]`.`[current_field]`" diff --git a/code/datums/_ndatabase/code/native_persistent_connection.dm b/code/datums/_ndatabase/code/native_persistent_connection.dm index e83417821d26..88b5eb22d130 100644 --- a/code/datums/_ndatabase/code/native_persistent_connection.dm +++ b/code/datums/_ndatabase/code/native_persistent_connection.dm @@ -32,17 +32,17 @@ if(!db) status = DB_CONNECTION_BROKEN return FALSE - + status = DB_CONNECTION_READY error_msg = null - + return TRUE /datum/db/connection/native/setup(_filename) filename = _filename if(!filename) filename = "local.db" - + db = new(filename) return TRUE @@ -60,8 +60,8 @@ query_number++ return pq return null - + /datum/db/connection/native/get_adapter() - var/datum/db/adapter/native_adapter/adapter = new() + var/datum/db/adapter/sql/native/adapter = new() adapter.connection = src return adapter diff --git a/code/datums/_ndatabase/code/sql_adapter.dm b/code/datums/_ndatabase/code/sql_adapter.dm new file mode 100644 index 000000000000..34f6b5e7be1c --- /dev/null +++ b/code/datums/_ndatabase/code/sql_adapter.dm @@ -0,0 +1,528 @@ +/** + * Queries with generated components around the filter (WHERE clause) + * + * Reserved for database structures with pre- and post- filter components that require + * more expensive (i.e. non-constant time) generation logic and only need to be generated + * once + */ +/datum/db/cached_query + var/datum/db/adapter/sql/native/adapter + /// Part of the query preceding the filter (optional) + var/pre_filter + /// Part of the query proceeding the filter (optional) + var/post_filter + /// Casts, because our query is already mapped to internal `T_n` structure, while whatever the hell we get is not + var/list/casts = list() + /// Parameters for the pre-filter component (if it exists) + var/list/pre_filter_query_parameters + /// Parameters for the post-filter component (if it exists) + var/list/post_filter_query_parameters + +/datum/db/cached_query/New(adapter, pre_filter, post_filter, casts, pre_filter_query_parameters, post_filter_query_parameters) + src.adapter = adapter + src.pre_filter = pre_filter + src.post_filter = post_filter + src.casts = casts + src.pre_filter_query_parameters = pre_filter_query_parameters + src.post_filter_query_parameters = post_filter_query_parameters + +/datum/db/cached_query/proc/spawn_query(datum/db/filter/F, list/query_parameters) + for(var/i in pre_filter_query_parameters) + query_parameters.Add(i) + var/query = "[pre_filter] [adapter.get_filter(F, casts, query_parameters)] [post_filter]" + for(var/i in post_filter_query_parameters) + query_parameters.Add(i) + return query + +GLOBAL_REAL(sql_constants, /datum/sql_constants) +/datum/sql_constants + var/const/SYSTABLENAME = "__entity_master" + var/const/SYSINDEXNAME = "__index_master" + var/const/BACKUP_PREFIX = "__backup_" + var/const/COUNT_COLUMN_NAME = "total" + var/const/ROOT_NAME = "__root" + var/const/ROOT_ALIAS = "T_root" + +/datum/db/adapter/sql + var/list/issue_log + var/list/datum/db/cached_query/cached_queries + +/datum/db/adapter/sql/New() + issue_log = list() + cached_queries = list() + +/datum/db/adapter/sql/read_table(table_name, list/ids, datum/callback/on_read_table_callback, sync = FALSE) + var/query_gettable = generate_select_table_query_text(table_name, ids) + if(sync) + SSdatabase.create_query_sync(query_gettable, on_read_table_callback) + else + SSdatabase.create_query(query_gettable, on_read_table_callback) + +/datum/db/adapter/sql/delete_table(table_name, list/ids, datum/callback/on_delete_table_callback, sync = FALSE) + var/query_deletetable = generate_delete_table_sql(table_name, ids) + if(sync) + SSdatabase.create_query_sync(query_deletetable, on_delete_table_callback) + else + SSdatabase.create_query(query_deletetable, on_delete_table_callback) + +/datum/db/adapter/sql/sync_table(type_name, table_name, list/datum/db_field/field_typepaths) + var/list/query_parameters = list() + var/query_gettable = generate_get_systable_query_text(table_name, query_parameters) + var/datum/db/query_response/table_meta = SSdatabase.create_parametric_query_sync(query_gettable, query_parameters) + if(table_meta.status != DB_QUERY_FINISHED) + issue_log += "Unable to access system table, error: '[table_meta.error]'" + return FALSE // OH SHIT OH FUCK + if(!length(table_meta.results)) // Table doesn't exist + return internal_create_table(table_name, field_typepaths) && internal_record_table_in_sys(type_name, table_name, field_typepaths) + + var/id = table_meta.results[1][DB_DEFAULT_ID_FIELD] + var/old_field_typepaths = savetext2fields(table_meta.results[1]["fields_current"]) + var/old_hash = table_meta.results[1]["fields_hash"] + var/field_text = fields2savetext(field_typepaths) + var/new_hash = sha1(field_text) + + if(old_hash == new_hash) + return TRUE // no action required + + var/tablecount = internal_table_count(table_name) + // check if we have any records + if(tablecount == 0) + // just MURDER IT + return internal_drop_table(table_name) && internal_create_table(table_name, field_typepaths) && internal_record_table_in_sys(type_name, table_name, field_typepaths, id) + return complete_sync(type_name, table_name, field_typepaths, old_field_typepaths, id) + +/datum/db/adapter/sql/proc/complete_sync(type_name, table_name, list/datum/db_field/field_typepaths, list/datum/db_field/old_field_typepaths, id) + PROTECTED_PROC(TRUE) + SHOULD_CALL_PARENT(FALSE) + CRASH("Database adapter [type] did not override `complete_sync` proc") + +/datum/db/adapter/sql/read_view(datum/entity_view_meta/view, datum/db/filter/filter, datum/callback/on_read_view_callback, sync=FALSE) + var/v_key = "v_[view.type]" + var/list/query_parameters = list() + var/datum/db/cached_query/cached_view = cached_queries[v_key] + if(!istype(cached_view)) + return null + var/query_getview = cached_view.spawn_query(filter, query_parameters) + if(sync) + SSdatabase.create_parametric_query_sync(query_getview, query_parameters, on_read_view_callback) + else + SSdatabase.create_parametric_query(query_getview, query_parameters, on_read_view_callback) + +/datum/db/adapter/sql/read_filter(table_name, datum/db/filter, datum/callback/on_read_filter_callback, sync = FALSE) + var/list/query_parameters = list() + var/query_gettable = generate_filter_table_query_text(table_name, filter, query_parameters) + if(sync) + SSdatabase.create_parametric_query_sync(query_gettable, query_parameters, on_read_filter_callback) + else + SSdatabase.create_parametric_query(query_gettable, query_parameters, on_read_filter_callback) + +/datum/db/adapter/sql/proc/internal_create_table(table_name, field_typepaths) + var/query = generate_create_table_sql(table_name, field_typepaths) + var/datum/db/query_response/sit_check = SSdatabase.create_query_sync(query) + if(sit_check.status != DB_QUERY_FINISHED) + issue_log += "Unable to create new table [table_name], error: '[sit_check.error]'" + return FALSE // OH SHIT OH FUCK + return TRUE + +/datum/db/adapter/sql/proc/internal_create_backup_table(table_name, field_typepaths) + var/query = generate_create_table_sql("[sql_constants::BACKUP_PREFIX][table_name]", field_typepaths) + var/datum/db/query_response/sit_check = SSdatabase.create_query_sync(query) + if(sit_check.status != DB_QUERY_FINISHED) + issue_log += "Unable to create backup for table [table_name], error: '[sit_check.error]'" + return FALSE // OH SHIT OH FUCK + return TRUE + +/datum/db/adapter/sql/proc/internal_drop_table(table_name) + var/query = generate_drop_table_sql(table_name) + var/datum/db/query_response/sit_check = SSdatabase.create_query_sync(query) + if(sit_check.status != DB_QUERY_FINISHED) + issue_log += "Unable to drop table [table_name], error: '[sit_check.error]'" + return FALSE // OH SHIT OH FUCK + return TRUE + +/datum/db/adapter/sql/proc/internal_drop_backup_table(table_name) + return internal_drop_table("[sql_constants::BACKUP_PREFIX][table_name]") + +/datum/db/adapter/sql/proc/internal_record_table_in_sys(type_name, table_name, field_typepaths, id) + var/list/query_parameters = list() + var/query = generate_systable_record_sql(type_name, table_name, field_typepaths, query_parameters, id) + var/datum/db/query_response/sit_check = SSdatabase.create_parametric_query_sync(query, query_parameters) + if(sit_check.status != DB_QUERY_FINISHED) + issue_log += "Unable to record meta for table [table_name], error: '[sit_check.error]'" + return FALSE // OH SHIT OH FUCK + return TRUE + +// returns -1 if shit is fucked, otherwise returns count +/datum/db/adapter/sql/proc/internal_table_count(table_name) + var/query = generate_get_rowcount_sql(table_name) + var/datum/db/query_response/rowcount = SSdatabase.create_query_sync(query) + if(rowcount.status != DB_QUERY_FINISHED) + issue_log += "Unable to get row count from table [table_name], error: '[rowcount.error]'" + return -1 // OH SHIT OH FUCK + return rowcount.results[1][sql_constants::COUNT_COLUMN_NAME] + +/datum/db/adapter/sql/proc/internal_migrate_table(table_name, list/datum/db_field/field_typepaths_old) + var/list/fields = list(DB_DEFAULT_ID_FIELD) + for(var/field in field_typepaths_old) + fields += field + + var/query = generate_insert_from_backup_sql(table_name, fields) + var/datum/db/query_response/sit_check = SSdatabase.create_query_sync(query) + if(sit_check.status != DB_QUERY_FINISHED) + issue_log += "Unable to migrate table [table_name] from backup, error: '[sit_check.error]'" + return FALSE // OH SHIT OH FUCK + return TRUE + +/datum/db/adapter/sql/proc/internal_migrate_to_backup(table_name, list/datum/db_field/field_typepaths_old) + var/list/fields = list(DB_DEFAULT_ID_FIELD) + for(var/field in field_typepaths_old) + fields += field + + var/query = generate_insert_into_backup_sql(table_name, fields) + var/datum/db/query_response/sit_check = SSdatabase.create_query_sync(query) + if(sit_check.status != DB_QUERY_FINISHED) + issue_log += "Unable to migrate table [table_name] to backup, error: '[sit_check.error]'" + return FALSE // OH SHIT OH FUCK + return TRUE + +/datum/db/adapter/sql/proc/internal_request_insert_allocation(table_name, size) + var/query = generate_allocate_insert_sql(table_name, size) + var/datum/db/query_response/first_id = SSdatabase.create_query_sync(query) + if(first_id.status != DB_QUERY_FINISHED) + issue_log += "Unable to allocate insert for [table_name], error: '[first_id.error]'" + return -1 // OH SHIT OH FUCK + var/value = first_id.results[1][sql_constants::COUNT_COLUMN_NAME] + if(!value) + return 1 + return value + +/datum/db/adapter/sql/proc/native_function_to_query_text(datum/db/native_function/native_function, list/field_alias, list/query_parameters) + switch(native_function.type) + if(/datum/db/native_function/case) + var/datum/db/native_function/case/case_f = native_function + var/result_true = case_f.result_true + var/result_false = case_f.result_false + var/condition_text = get_filter(case_f.condition, field_alias, query_parameters) + var/true_query_text + if(result_true) + if(istype(result_true, /datum/db/native_function)) + true_query_text = native_function_to_query_text(result_true, field_alias, query_parameters) + else + var/field_cast = "[result_true]" + if(field_alias && field_alias[field_cast]) + field_cast = field_alias[field_cast] + true_query_text = field_cast + var/false_query_text + if(result_false) + if(istype(result_false, /datum/db/native_function)) + false_query_text = native_function_to_query_text(result_false, field_alias, query_parameters) + else + var/field_cast = "[result_false]" + if(field_alias && field_alias[field_cast]) + field_cast = field_alias[field_cast] + false_query_text = field_cast + if(false_query_text) + false_query_text = "ELSE ([false_query_text]) " + return "CASE WHEN [condition_text] THEN ([true_query_text]) [false_query_text] END" + else + return native_function.default_to_string(field_alias, query_parameters) + +/// Generate SQL code to create the system table if it does not exist. +/datum/db/adapter/sql/proc/generate_systable_check_sql() + return {" + CREATE TABLE IF NOT EXISTS [table_name_to_sql(sql_constants::SYSTABLENAME)] ([get_systable_fields_sql()]) + "} + +/// Generate SQL code for the column definition of the `CREATE TABLE` SQL command for the system table. +/datum/db/adapter/sql/proc/get_systable_fields_sql() + PROTECTED_PROC(TRUE) + SHOULD_CALL_PARENT(FALSE) + CRASH("Database adapter [type] did not override `get_systable_fields_sql` proc") + +/// Generate SQL code to query for a specific entity table from the system table. +/datum/db/adapter/sql/proc/generate_get_systable_query_text(table_name, list/query_parameters) + query_parameters.Add("[table_name]") + return {" + SELECT id, type_name, table_name, fields_hash, fields_current FROM [table_name_to_sql(sql_constants::SYSTABLENAME)] WHERE table_name = ? + "} + +/// Generate SQL code to create an entity table. +/datum/db/adapter/sql/proc/generate_create_table_sql(table_name, field_typepaths) + return {" + CREATE TABLE [table_name_to_sql(table_name)] ( + [get_table_metadata_fields_sql()], [fields2text(field_typepaths)] + ); + "} + +/// Generate SQL code for the metadata fields of an entity table. +/// Placed in the beginning of the column definition of the `CREATE TABLE` SQL command for the entity table. +/datum/db/adapter/sql/proc/get_table_metadata_fields_sql() + PROTECTED_PROC(TRUE) + SHOULD_CALL_PARENT(FALSE) + CRASH("Database adapter [type] did not override `get_table_metadata_fields_sql` proc") + +/// Generate SQL code to delete a table +/datum/db/adapter/sql/proc/generate_delete_table_sql(table_name, list/ids) + var/idtext = "" + var/first = TRUE + for(var/id in ids) + if(!first) + idtext+="," + idtext += "[text2num(id)]" + first = FALSE + return {" + DELETE FROM [table_name_to_sql(table_name)] WHERE id IN ([idtext]); + "} + +/// Generate SQL code to insert a row from an entity table into its backup table +/datum/db/adapter/sql/proc/generate_insert_into_backup_sql(table_name, list/fields) + var/field_text = jointext(fields, ", ") + return {" + INSERT INTO [table_name_to_sql("[sql_constants::BACKUP_PREFIX][table_name]")] ([field_text]) + SELECT [field_text] FROM [table_name_to_sql(table_name)] + "} + +/// Generate SQL code to insert a row into an entity table from its backup table +/datum/db/adapter/sql/proc/generate_insert_from_backup_sql(table_name, list/fields) + var/field_text = jointext(fields, ", ") + return {" + INSERT INTO [table_name_to_sql(table_name)] ([field_text]) + SELECT [field_text] FROM [table_name_to_sql("[sql_constants::BACKUP_PREFIX][table_name]")] + "} + +/// Generate SQL code to record a table in the system table. +/datum/db/adapter/sql/proc/generate_systable_record_sql(type_name, table_name, field_typepaths, list/query_parameters, id = null) + var/field_text = fields2savetext(field_typepaths) + var/new_hash = sha1(field_text) + query_parameters.Add("[type_name]") + query_parameters.Add("[table_name]") + query_parameters.Add("[new_hash]") + query_parameters.Add("[field_text]") + if(!id) + return {" + INSERT INTO [table_name_to_sql(sql_constants::SYSTABLENAME)] (type_name, table_name, fields_hash, fields_current) VALUES (?, ?, ?, ?); + "} + + return {" + UPDATE [table_name_to_sql(sql_constants::SYSTABLENAME)]` SET type_name = ?, table_name = ?, fields_hash = ?, fields_current= ? WHERE id=[text2num(id)]; + "} + +/// Generate SQL code to allocate a row for insertion on a table. +/datum/db/adapter/sql/proc/generate_allocate_insert_sql(table_name, size) + return {" + SELECT max(id) + 1 as total FROM [table_name_to_sql(table_name)] + "} + +/// Generate SQL code to get the row count of a table. +/datum/db/adapter/sql/proc/generate_get_rowcount_sql(table_name) + return {" + SELECT count(1) as [sql_constants::COUNT_COLUMN_NAME] FROM [table_name_to_sql(table_name)] + "} + +/datum/db/adapter/sql/proc/generate_drop_table_sql(table_name) + return {" + DROP TABLE IF EXISTS [table_name_to_sql(table_name)] + "} + +/datum/db/adapter/sql/proc/generate_select_table_query_text(table_name, list/ids, list/fields) + var/id_text = "" + var/first = TRUE + for(var/id in ids) + if(!first) + id_text+="," + first = FALSE + id_text += "[text2num(id)]" // against some pesky injections + return {" + SELECT [fields?(""+jointext(fields,",")+""):"*"] FROM [table_name_to_sql(table_name)] WHERE id in ([id_text]) + "} + +/datum/db/adapter/sql/proc/generate_filter_table_query_text(table_name, datum/db/filter, list/query_parameters, list/fields) + return {" + SELECT [fields?(""+jointext(fields,",")+""):"*"] FROM [table_name_to_sql(table_name)] WHERE [get_filter(filter, null, query_parameters)] + "} + +/datum/db/adapter/sql/proc/parse_column(field, field_value, datum/entity_view_meta/view, list/shared_options, list/datum/entity_meta/meta_to_load, list/meta_to_table, list/datum/db/filter/join_conditions, list/field_alias) + PRIVATE_PROC(TRUE) + if (istype(field_value, /datum/db/native_function)) + var/datum/db/native_function/native_function = field_value + var/list/field_list = native_function.get_columns() + + for(var/sub_field in field_list) + parse_column(sub_field, sub_field, view, shared_options, meta_to_load, meta_to_table, join_conditions, field_alias) + if(field) + field_alias[field] = native_function + + return + // no, this is a normal field + // already parsed? + if (field && field_alias[field]) + return + + var/datum/entity_meta/current_root = view.root_entity_meta + var/current_field = field_value + var/current_path = "" + var/current_alias = meta_to_table[sql_constants::ROOT_NAME] + var/link_loc = findtextEx(current_field, ".") + while (link_loc) + var/table_link = COPY_FROM_START(current_field, link_loc) + if (!table_link) + break + if (current_path) + current_path += "." + table_link + else + current_path = table_link + current_field = COPY_AFTER_FOUND(current_field, link_loc) + link_loc = findtextEx(current_field, ".") + if (!meta_to_table[current_path]) + var/step_child = "T_[shared_options["table_alias_id"]]" + meta_to_table[current_path] = step_child + shared_options["table_alias_id"]++ + var/datum/entity_link/next_link = current_root.outbound_links[table_link] + if (next_link) + meta_to_load[current_path] = next_link.parent_meta + join_conditions[step_child] = next_link.get_filter(step_child, current_alias) + else + next_link = current_root.inbound_links[table_link] + if (!next_link) + return FALSE // failed to initialize view + meta_to_load[current_path] = next_link.child_meta + join_conditions[step_child] = next_link.get_filter(current_alias, step_child) + + current_root = meta_to_load[current_path] + current_alias = meta_to_table[current_path] + + if (field) + field_alias[field] = "`[current_alias]`.`[current_field]`" + +/datum/db/adapter/sql/prepare_view(datum/entity_view_meta/view) + /// Maps an entity node to the table name + var/list/datum/entity_meta/meta_to_load = list(sql_constants::ROOT_NAME = view.root_entity_meta) + var/list/meta_to_table = list(sql_constants::ROOT_NAME = sql_constants::ROOT_ALIAS) + var/list/datum/db/filter/join_conditions = list() + var/list/field_alias = list() + var/list/shared_options = list() + shared_options["table_alias_id"] = 1 + for (var/field in view.fields) + var/field_path = view.fields[field] + if (!field_path) + field_path = field + parse_column(field, field_path, view, shared_options, meta_to_load, meta_to_table, join_conditions, field_alias) + + for (var/field in view.group_by) + var/field_path = view.fields[field] + if (!field_path) + field_path = field + parse_column(field, field_path, view, shared_options, meta_to_load, meta_to_table, join_conditions, field_alias) + + if (view.root_filter) + var/list/filter_columns = view.root_filter.get_columns() + for (var/field in filter_columns) + parse_column(field, field, view, shared_options, meta_to_load, meta_to_table, join_conditions, field_alias) + + generate_view_query(view, shared_options, meta_to_load, meta_to_table, join_conditions, field_alias) + +/datum/db/adapter/sql/proc/table_name_to_sql(table_name) + PROTECTED_PROC(TRUE) + return "`[table_name]`" + +/datum/db/adapter/sql/proc/generate_view_query(datum/entity_view_meta/view, list/shared_options, list/datum/entity_meta/meta_to_load, list/meta_to_table, list/datum/db/filter/join_conditions, list/field_alias) + PRIVATE_PROC(TRUE) + var/list/pre_filter_query_parameters = list() + var/query_text = "SELECT " + for (var/fld in view.fields) + var/field = field_alias[fld] + var/datum/db/native_function/native_function = field + // this is a function? + if(istype(native_function)) + field = native_function_to_query_text(native_function, field_alias, pre_filter_query_parameters) + query_text += "[field] as `[fld]`, " + query_text += "1 as `is_view` " + query_text += "FROM `[meta_to_load[sql_constants::ROOT_NAME].table_name]` as `[meta_to_table[sql_constants::ROOT_NAME]]` " + var/join_text = "" + for (var/mtt in meta_to_table) + if(mtt == sql_constants::ROOT_NAME) + continue + var/table_alias = meta_to_table[mtt] + var/table_name = meta_to_load[mtt].table_name + var/join_condition_text = get_filter(join_conditions[table_alias], null, pre_filter_query_parameters) + join_text += "LEFT JOIN [table_name_to_sql(table_name)] as `[table_alias]` on [join_condition_text] " + + query_text += join_text + + query_text += "WHERE " + + var/pre_filter = query_text + + var/list/post_filter_query_parameters = list() + + query_text = "" + + if (view.root_filter) + var/filter_text = get_filter(view.root_filter, field_alias, post_filter_query_parameters) + query_text += "AND ([filter_text]) " + + var/order_length = length(view.order_by) + + if (order_length) + var/order_text = "ORDER BY " + var/index_order = 1 + for (var/order_field in view.order_by) + var/order_d = view.order_by[order_field] + order_text += "[field_alias[order_field]] " + if (order_d == DB_ORDER_BY_ASC) + order_text += "ASC" + if (order_d == DB_ORDER_BY_DESC) + order_text += "DESC" + if (index_order != order_length) + order_text += "," + index_order++ + query_text += order_text + + var/group_length = length(view.group_by) + + if (group_length) + var/group_text = "GROUP BY " + var/index_order = 1 + for (var/fld in view.group_by) + var/field = field_alias[fld] + group_text += "[field]" + if (index_order != group_length) + group_text += "," + query_text += group_text + " " + + var/post_filter = query_text + var/key = "v_[view.type]" + cached_queries[key] = new /datum/db/cached_query(src, pre_filter, post_filter, field_alias, pre_filter_query_parameters, post_filter_query_parameters) + +/datum/db/adapter/sql/proc/fieldtype2text(type_id) + PROTECTED_PROC(TRUE) + SHOULD_CALL_PARENT(FALSE) + CRASH("Database adapter [type] did not override `fieldtype2text` proc") + +/datum/db/adapter/sql/proc/fields2text(list/datum/db_field/field_typepaths) + var/list/result = list() + for(var/field in field_typepaths) + var/datum/db_field/field_typepath = field_typepaths[field] + result += "[field_typepath::name] [fieldtype2text(field_typepath::field_type)]" + return jointext(result, ",") + +/datum/db/adapter/sql/proc/fields2savetext(list/datum/db_field/field_typepaths) + var/list/result = list() + for(var/field in field_typepaths) + var/datum/db_field/field_typepath = field_typepaths[field] + result += "[field_typepath::name]:[field_typepath::field_type::type_id]" + return jointext(result, ",") + +#define FIELD_NAME_IDX 1 +#define FIELD_TYPE_ID_IDX 2 +/datum/db/adapter/sql/proc/savetext2fields(text) + PRIVATE_PROC(TRUE) + var/list/result = list() + var/list/fields_as_savetext = splittext(text, ",") + for(var/field_as_savetext in fields_as_savetext) + var/list/field_information = splittext(field_as_savetext, ":") + var/field_type_id = text2num(field_information[FIELD_TYPE_ID_IDX]) + result[field_information[FIELD_NAME_IDX]] = GLOB.db_field_types[field_type_id] + return result +#undef FIELD_NAME_IDX +#undef FIELD_TYPE_ID_IDX diff --git a/code/datums/_ndatabase/include.dm b/code/datums/_ndatabase/include.dm index f0c48ef128fc..a089743af66f 100644 --- a/code/datums/_ndatabase/include.dm +++ b/code/datums/_ndatabase/include.dm @@ -1,5 +1,7 @@ #include "code/database.dm" +#include "code/sql_adapter.dm" + #ifdef NDATABASE_RUSTG_BRSQL_SUPPORT #include "code/brsql_persistent_connection.dm" #include "code/brsql_persistent_query.dm" @@ -18,6 +20,8 @@ #include "code/entity/link.dm" #include "code/entity/entity_view.dm" #include "code/entity/index.dm" +#include "code/entity/db_field.dm" +#include "code/entity/db_field_type.dm" #include "code/interfaces/adapter.dm" #include "code/interfaces/connection.dm" diff --git a/code/datums/_ndatabase/subsystems/entity_manager.dm b/code/datums/_ndatabase/subsystems/entity_manager.dm index 74dab079a1e6..5cb6e543b29d 100644 --- a/code/datums/_ndatabase/subsystems/entity_manager.dm +++ b/code/datums/_ndatabase/subsystems/entity_manager.dm @@ -185,8 +185,8 @@ GLOBAL_REAL(SSentity_manager, /datum/controller/subsystem/entity_manager) return meta.to_read = list() // release the list early var/list/ids = list() - for(var/datum/entity/item in to_select) - ids += item.id + for(var/datum/entity/entity in to_select) + ids += entity.id adapter.read_table(meta.table_name, ids, CALLBACK(src, TYPE_PROC_REF(/datum/controller/subsystem/entity_manager, after_select), meta, to_select)) diff --git a/colonialmarines.dme b/colonialmarines.dme index 371fd65d5431..3ffc2e66ac8d 100644 --- a/colonialmarines.dme +++ b/colonialmarines.dme @@ -368,8 +368,6 @@ #include "code\datums\weakrefs.dm" #include "code\datums\world_topic.dm" #include "code\datums\_ndatabase\include.dm" -#include "code\datums\_ndatabase\code\entity\db_field.dm" -#include "code\datums\_ndatabase\code\entity\db_field_type.dm" #include "code\datums\agents\tools.dm" #include "code\datums\agents\tools\access_tuner.dm" #include "code\datums\agents\tools\badass_kit.dm"