diff --git a/code/__DEFINES/_tick.dm b/code/__DEFINES/_tick.dm index 20eb648fe6..10b815e547 100644 --- a/code/__DEFINES/_tick.dm +++ b/code/__DEFINES/_tick.dm @@ -7,4 +7,7 @@ #define TICK_USAGE_REAL world.tick_usage //to be used where the result isn't checked #define TICK_CHECK ( TICK_USAGE > Master.current_ticklimit ) -#define CHECK_TICK if TICK_CHECK stoplag() +#define CHECK_TICK ( TICK_CHECK ? stoplag() : 0 ) + +#define TICK_CHECK_HIGH_PRIORITY ( TICK_USAGE > 95 ) +#define CHECK_TICK_HIGH_PRIORITY ( TICK_CHECK_HIGH_PRIORITY? stoplag() : 0 ) diff --git a/code/__DEFINES/flags.dm b/code/__DEFINES/flags.dm index 1b64ce7e6c..ad9aef5a8b 100644 --- a/code/__DEFINES/flags.dm +++ b/code/__DEFINES/flags.dm @@ -4,6 +4,11 @@ #define ALL (~0) //For convenience. #define NONE 0 +//for convenience +#define ENABLE_BITFIELD(variable, flag) (variable |= (flag)) +#define DISABLE_BITFIELD(variable, flag) (variable &= ~(flag)) +#define CHECK_BITFIELD(variable, flag) (variable & flag) + GLOBAL_LIST_INIT(bitflags, list(1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, 2048, 4096, 8192, 16384, 32768)) // for /datum/var/datum_flags diff --git a/code/__HELPERS/time.dm b/code/__HELPERS/time.dm index 3ffb583332..aaf7ced922 100644 --- a/code/__HELPERS/time.dm +++ b/code/__HELPERS/time.dm @@ -63,96 +63,29 @@ GLOBAL_VAR_INIT(rollovercheck_last_timeofday, 0) //Takes a value of time in deciseconds. //Returns a text value of that number in hours, minutes, or seconds. -/proc/DisplayTimeText(time_value, truncate = FALSE) - var/second = (time_value)*0.1 - var/second_adjusted = null - var/second_rounded = FALSE - var/minute = null - var/hour = null - var/day = null - +/proc/DisplayTimeText(time_value, round_seconds_to = 0.1) + var/second = round(time_value * 0.1, round_seconds_to) if(!second) - return "0 seconds" - if(second >= 60) - minute = FLOOR(second/60, 1) - second = round(second - (minute*60), 0.1) - second_rounded = TRUE - if(second) //check if we still have seconds remaining to format, or if everything went into minute. - second_adjusted = round(second) //used to prevent '1 seconds' being shown - if(day || hour || minute) - if(second_adjusted == 1 && second >= 1) - second = " and 1 second" - else if(second > 1) - second = " and [second_adjusted] seconds" - else //shows a fraction if seconds is < 1 - if(second_rounded) //no sense rounding again if it's already done - second = " and [second] seconds" - else - second = " and [round(second, 0.1)] seconds" - else - if(second_adjusted == 1 && second >= 1) - second = "[truncate ? "second" : "1 second"]" - else if(second > 1) - second = "[second_adjusted] seconds" - else - if(second_rounded) - second = "[second] seconds" - else - second = "[round(second, 0.1)] seconds" - else - second = null - - if(!minute) - return "[second]" - if(minute >= 60) - hour = FLOOR(minute/60, 1) - minute = (minute - (hour*60)) - if(minute) //alot simpler from here since you don't have to worry about fractions - if(minute != 1) - if((day || hour) && second) - minute = ", [minute] minutes" - else if((day || hour) && !second) - minute = " and [minute] minutes" - else - minute = "[minute] minutes" - else - if((day || hour) && second) - minute = ", 1 minute" - else if((day || hour) && !second) - minute = " and 1 minute" - else - minute = "[truncate ? "minute" : "1 minute"]" - else - minute = null - - if(!hour) - return "[minute][second]" - if(hour >= 24) - day = FLOOR(hour/24, 1) - hour = (hour - (day*24)) + return "right now" + if(second < 60) + return "[second] second[(second != 1)? "s":""]" + var/minute = FLOOR(second / 60, 1) + second = MODULUS(second, 60) + var/secondT + if(second) + secondT = " and [second] second[(second != 1)? "s":""]" + if(minute < 60) + return "[minute] minute[(minute != 1)? "s":""][secondT]" + var/hour = FLOOR(minute / 60, 1) + minute = MODULUS(minute, 60) + var/minuteT + if(minute) + minuteT = " and [minute] minute[(minute != 1)? "s":""]" + if(hour < 24) + return "[hour] hour[(hour != 1)? "s":""][minuteT][secondT]" + var/day = FLOOR(hour / 24, 1) + hour = MODULUS(hour, 24) + var/hourT if(hour) - if(hour != 1) - if(day && (minute || second)) - hour = ", [hour] hours" - else if(day && (!minute || !second)) - hour = " and [hour] hours" - else - hour = "[hour] hours" - else - if(day && (minute || second)) - hour = ", 1 hour" - else if(day && (!minute || !second)) - hour = " and 1 hour" - else - hour = "[truncate ? "hour" : "1 hour"]" - else - hour = null - - if(!day) - return "[hour][minute][second]" - if(day > 1) - day = "[day] days" - else - day = "[truncate ? "day" : "1 day"]" - - return "[day][hour][minute][second]" + hourT = " and [hour] hour[(hour != 1)? "s":""]" + return "[day] day[(day != 1)? "s":""][hourT][minuteT][secondT]" diff --git a/code/modules/admin/verbs/SDQL2/SDQL_2.dm b/code/modules/admin/verbs/SDQL2/SDQL_2.dm index 603e4daf2d..06a1362b38 100644 --- a/code/modules/admin/verbs/SDQL2/SDQL_2.dm +++ b/code/modules/admin/verbs/SDQL2/SDQL_2.dm @@ -1,23 +1,195 @@ -// Code taken from /bay/station. -// Modified to allow consequtive querys in one invocation, terminated with ";" +#define SDQL_qdel_datum(d) qdel(d) + +//SDQL2 datumized, /tg/station special! -// Examples /* - -- Will call the proc for all computers in the world, thats dir is 2. - CALL ex_act(EXPLODE_DEVASTATE) ON /obj/machinery/computer IN world WHERE dir == 2 - -- Will open a window with a list of all the closets in the world, with a link to VV them. - SELECT /obj/structure/closet/secure_closet/security/cargo IN world WHERE icon_off == "secoff" - -- Will change all the tube lights to green - UPDATE /obj/machinery/light IN world SET color = "#0F0" WHERE icon_state == "tube1" - -- Will delete all pickaxes. "IN world" is not required. - DELETE /obj/item/pickaxe - -- Will flicker the lights once, then turn all mobs green. The semicolon is important to separate the consecutive querys, but is not required for standard one-query use - CALL flicker(1) ON /obj/machinery/light; UPDATE /mob SET color = "#00cc00" + Welcome admins, badmins and coders alike, to Structured Datum Query Language. + SDQL allows you to powerfully run code on batches of objects (or single objects, it's still unmatched + even there.) + When I say "powerfully" I mean it you're in for a ride. - --You can use operators other than ==, such as >, <=, != and etc.. + Ok so say you want to get a list of every mob. How does one do this? + "SELECT /mob" + This will open a list of every object in world that is a /mob. + And you can VV them if you need. + + What if you want to get every mob on a *specific z-level*? + "SELECT /mob WHERE z == 4" + + What if you want to select every mob on even numbered z-levels? + "SELECT /mob WHERE z % 2 == 0" + + Can you see where this is going? You can select objects with an arbitrary expression. + These expressions can also do variable access and proc calls (yes, both on-object and globals!) + Keep reading! + + Ok. What if you want to get every machine in the SSmachine process list? Looping through world is kinda + slow. + + "SELECT * IN SSmachines.machinery" + + Here "*" as type functions as a wildcard. + We know everything in the global SSmachines.machinery list is a machine. + + You can specify "IN " to return a list to operate on. + This can be any list that you can wizard together from global variables and global proc calls. + Every variable/proc name in the "IN" block is global. + It can also be a single object, in which case the object is wrapped in a list for you. + So yeah SDQL is unironically better than VV for complex single-object operations. + + You can of course combine these. + "SELECT * IN SSmachines.machinery WHERE z == 4" + "SELECT * IN SSmachines.machinery WHERE stat & 2" // (2 is NOPOWER, can't use defines from SDQL. Sorry!) + "SELECT * IN SSmachines.machinery WHERE stat & 2 && z == 4" + + The possibilities are endless (just don't crash the server, ok?). + + Oh it gets better. + + You can use "MAP " to run some code per object and use the result. For example: + + "SELECT /obj/machinery/power/smes MAP [charge / capacity * 100, RCon_tag, src]" + + This will give you a list of all the APCs, their charge AND RCon tag. Useful eh? + + [] being a list here. Yeah you can write out lists directly without > lol lists in VV. Color matrix + shenanigans inbound. + + After the "MAP" segment is executed, the rest of the query executes as if it's THAT object you just made + (here the list). + Yeah, by the way, you can chain these MAP / WHERE things FOREVER! + + "SELECT /mob WHERE client MAP client WHERE holder MAP holder" + + What if some dumbass admin spawned a bajillion spiders and you need to kill them all? + Oh yeah you'd rather not delete all the spiders in maintenace. Only that one room the spiders were + spawned in. + + "DELETE /mob/living/carbon/superior_animal/giant_spider WHERE loc.loc == marked" + + Here I used VV to mark the area they were in, and since loc.loc = area, voila. + Only the spiders in a specific area are gone. + + Or you know if you want to catch spiders that crawled into lockers too (how even?) + + "DELETE /mob/living/carbon/superior_animal/giant_spider WHERE global.get_area(src) == marked" + + What else can you do? + + Well suppose you'd rather gib those spiders instead of simply flat deleting them... + + "CALL gib() ON /mob/living/carbon/superior_animal/giant_spider WHERE global.get_area(src) == marked" + + Or you can have some fun.. + + "CALL forceMove(marked) ON /mob/living/carbon/superior_animal" + + You can also run multiple queries sequentially: + + "CALL forceMove(marked) ON /mob/living/carbon/superior_animal; CALL gib() ON + /mob/living/carbon/superior_animal" + + And finally, you can directly modify variables on objects. + + "UPDATE /mob WHERE client SET client.color = [0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0]" + + Don't crash the server, OK? + + A quick recommendation: before you run something like a DELETE or another query.. Run it through SELECT + first. + You'd rather not gib every player on accident. + Or crash the server. + + By the way, queries are slow and take a while. Be patient. + They don't hang the entire server though. + + With great power comes great responsability. + + Here's a slightly more formal quick reference. + + The 4 queries you can do are: + + "SELECT " + "CALL ON " + "UPDATE SET var=,var2=" + "DELETE " + + "" in this context is " [IN ] [chain of MAP/WHERE modifiers]" + + "IN" (or "FROM", that works too but it's kinda weird to read), + is the list of objects to work on. This defaults to world if not provided. + But doing something like "IN living_mob_list" is quite handy and can optimize your query. + All names inside the IN block are global scope, so you can do living_mob_list (a global var) easily. + You can also run it on a single object. Because SDQL is that convenient even for single operations. + + filters out objects of, well, that type easily. "*" is a wildcard and just takes everything in + the source list. + + And then there's the MAP/WHERE chain. + These operate on each individual object being ran through the query. + They're both expressions like IN, but unlike it the expression is scoped *on the object*. + So if you do "WHERE z == 4", this does "src.z", effectively. + If you want to access global variables, you can do `global.living_mob_list`. + Same goes for procs. + + MAP "changes" the object into the result of the expression. + WHERE "drops" the object if the expression is falsey (0, null or "") + + What can you do inside expressions? + + * Proc calls + * Variable reads + * Literals (numbers, strings, type paths, etc...) + * \ref referencing: {0x30000cc} grabs the object with \ref [0x30000cc] + * Lists: [a, b, c] or [a: b, c: d] + * Math and stuff. + * A few special variables: src (the object currently scoped on), usr (your mob), + marked (your marked datum), global(global scope) + + TG ADDITIONS START: + Add USING keyword to the front of the query to use options system + The defaults aren't necessarily implemented, as there is no need to. + Available options: (D) means default + PROCCALL = (D)ASYNC, BLOCKING + SELECT = FORCE_NULLS, (D)SKIP_NULLS + PRIORITY = HIGH, (D) NORMAL + AUTOGC = (D) AUTOGC, KEEP_ALIVE + + Example: USING PROCCALL = BLOCKING, SELECT = FORCE_NULLS, PRIORITY = HIGH SELECT /mob FROM world WHERE z == 1 */ + +#define SDQL2_STATE_ERROR 0 +#define SDQL2_STATE_IDLE 1 +#define SDQL2_STATE_PRESEARCH 2 +#define SDQL2_STATE_SEARCHING 3 +#define SDQL2_STATE_EXECUTING 4 +#define SDQL2_STATE_SWITCHING 5 +#define SDQL2_STATE_HALTING 6 + +#define SDQL2_VALID_OPTION_TYPES list("proccall", "select", "priority", "autogc") +#define SDQL2_VALID_OPTION_VALUES list("async", "blocking", "force_nulls", "skip_nulls", "high", "normal", "keep_alive") + +#define SDQL2_OPTION_SELECT_OUTPUT_SKIP_NULLS (1<<0) +#define SDQL2_OPTION_BLOCKING_CALLS (1<<1) +#define SDQL2_OPTION_HIGH_PRIORITY (1<<2) //High priority SDQL query, allow using almost all of the tick. +#define SDQL2_OPTION_DO_NOT_AUTOGC (1<<3) + +#define SDQL2_OPTIONS_DEFAULT (SDQL2_OPTION_SELECT_OUTPUT_SKIP_NULLS) + +#define SDQL2_IS_RUNNING (state == SDQL2_STATE_EXECUTING || state == SDQL2_STATE_SEARCHING || state == SDQL2_STATE_SWITCHING || state == SDQL2_STATE_PRESEARCH) +#define SDQL2_HALT_CHECK if(!SDQL2_IS_RUNNING) {state = SDQL2_STATE_HALTING; return FALSE;}; + +#define SDQL2_TICK_CHECK ((options & SDQL2_OPTION_HIGH_PRIORITY)? CHECK_TICK_HIGH_PRIORITY : CHECK_TICK) + +#define SDQL2_STAGE_SWITCH_CHECK if(state != SDQL2_STATE_SWITCHING){\ + if(state == SDQL2_STATE_HALTING){\ + state = SDQL2_STATE_IDLE;\ + return FALSE}\ + state = SDQL2_STATE_ERROR;\ + CRASH("SDQL2 fatal error");}; + /client/proc/SDQL2_query(query_text as message) set category = "Debug" if(!check_rights(R_DEBUG)) //Shouldn't happen... but just to be safe. @@ -25,139 +197,685 @@ log_admin("Non-admin [key_name(usr)] attempted to execute a SDQL query!") return FALSE var/list/results = world.SDQL2_query(query_text, key_name_admin(usr), "[key_name(usr)]") - for(var/I in 1 to 3) - to_chat(usr, results[I]) + if(length(results) == 3) + for(var/I in 1 to 3) + to_chat(usr, results[I]) SSblackbox.record_feedback("nested tally", "SDQL query", 1, list(ckey, query_text)) /world/proc/SDQL2_query(query_text, log_entry1, log_entry2) - var/query_log = "executed SDQL query: \"[query_text]\"." + var/query_log = "executed SDQL query(s): \"[query_text]\"." message_admins("[log_entry1] [query_log]") query_log = "[log_entry2] [query_log]" log_game(query_log) NOTICE(query_log) + + var/start_time_total = REALTIMEOFDAY + + if(!length(query_text)) + return + var/list/query_list = SDQL2_tokenize(query_text) + if(!length(query_list)) + return + var/list/querys = SDQL_parse(query_list) + if(!length(querys)) + return + var/list/datum/SDQL2_query/running = list() + for(var/list/query_tree in querys) + var/datum/SDQL2_query/query = new /datum/SDQL2_query(query_tree) + if(QDELETED(query)) + continue + running += query + var/msg = "Starting query #[query.id] - [query.get_query_text()]." + if(usr) + to_chat(usr, "[msg]") + log_admin(msg) + query.ARun() + var/finished = FALSE var/objs_all = 0 var/objs_eligible = 0 - var/start_time = REALTIMEOFDAY - - if(!query_text || length(query_text) < 1) - return - - - var/list/query_list = SDQL2_tokenize(query_text) - - if(!query_list || query_list.len < 1) - return - - var/list/querys = SDQL_parse(query_list) - - - if(!querys || querys.len < 1) - return - - var/list/refs = list() - var/where_used = FALSE - for(var/list/query_tree in querys) - var/list/from_objs = list() - var/list/select_types = list() - - switch(query_tree[1]) - if("explain") - SDQL_testout(query_tree["explain"]) - return - - if("call") - if("on" in query_tree) - select_types = query_tree["on"] + var/selectors_used = FALSE + var/list/combined_refs = list() + do + CHECK_TICK + finished = TRUE + for(var/i in running) + var/datum/SDQL2_query/query = i + if(QDELETED(query)) + running -= query + continue + else if(query.state != SDQL2_STATE_IDLE) + finished = FALSE + else if(query.state == SDQL2_STATE_ERROR) + if(usr) + to_chat(usr, "SDQL query [query.get_query_text()] errored. It will NOT be automatically garbage collected. Please remove manually.") + running -= query + else + if(query.finished) + if(length(query.select_text)) + var/text = islist(query.select_text)? query.select_text.Join() : query.select_text + var/static/result_offset = 0 + usr << browse(text, "window=SDQL-result-[result_offset++]") + objs_all += islist(query.obj_count_all)? length(query.obj_count_all) : query.obj_count_all + objs_eligible += islist(query.obj_count_eligible)? length(query.obj_count_eligible) : query.obj_count_eligible + selectors_used |= query.where_switched + combined_refs |= query.select_refs + if(usr) + to_chat(usr, "SDQL query results: [query.get_query_text()]
\ + SDQL query completed: [islist(query.obj_count_all)? length(query.obj_count_all) : query.obj_count_all] objects selected by path, and \ + [query.where_switched? "[islist(query.obj_count_eligible)? length(query.obj_count_eligible) : query.obj_count_eligible] objects executed on after WHERE keyword selection." : ""]
\ + SDQL query took [DisplayTimeText(query.end_time - query.start_time)] to complete.
") + running -= query + if(!CHECK_BITFIELD(query.options, SDQL2_OPTION_DO_NOT_AUTOGC)) + QDEL_IN(query, 50) else - return + if(usr) + to_chat(usr, "SDQL query [query.get_query_text()] was halted. It will NOT be automatically garbage collected. Please remove manually.") + running -= query + while(!finished) - if("select", "delete", "update") - select_types = query_tree[query_tree[1]] + var/end_time_total = REALTIMEOFDAY - start_time_total + return list("SDQL query combined results: [query_text]",\ + "SDQL query completed: [objs_all] objects selected by path, and [selectors_used ? objs_eligible : objs_all] objects executed on after WHERE filtering/MAPping if applicable.",\ + "SDQL combined querys took [DisplayTimeText(end_time_total)] to complete.") + combined_refs - from_objs = world.SDQL_from_objs(query_tree["from"]) +GLOBAL_LIST_INIT(sdql2_queries, GLOB.sdql2_queries || list()) +GLOBAL_DATUM_INIT(sdql2_vv_statobj, /obj/effect/statclick/SDQL2_VV_all, new(null, "VIEW VARIABLES (all)", null)) - var/list/objs = list() +/datum/SDQL2_query + var/list/query_tree + var/state = SDQL2_STATE_IDLE + var/options = SDQL2_OPTIONS_DEFAULT + var/superuser = FALSE //Run things like proccalls without using admin protections + var/allow_admin_interact = TRUE //Allow admins to do things to this excluding varedit these two vars + var/static/id_assign = 1 + var/id = 0 - for(var/type in select_types) - objs += SDQL_get_all(type, from_objs) - CHECK_TICK - objs_all = objs.len + var/qdel_on_finish = FALSE - if("where" in query_tree) - where_used = TRUE - var/objs_temp = objs - objs = list() - for(var/datum/d in objs_temp) - if(SDQL_expression(d, query_tree["where"])) - objs += d - objs_eligible++ - CHECK_TICK + //Last run + //General + var/finished = FALSE + var/start_time + var/end_time + var/where_switched = FALSE + //Select query only + var/list/select_refs + var/list/select_text + //Runtime tracked + //These three are weird. For best performance, they are only a number when they're not being changed by the SDQL searching/execution code. They only become numbers when they finish changing. + var/list/obj_count_all + var/list/obj_count_eligible + var/list/obj_count_finished - switch(query_tree[1]) - if("call") - for(var/datum/d in objs) - world.SDQL_var(d, query_tree["call"][1], source = d) - CHECK_TICK + //Statclick + var/obj/effect/statclick/SDQL2_delete/delete_click + var/obj/effect/statclick/SDQL2_action/action_click - if("delete") - for(var/datum/d in objs) - SDQL_qdel_datum(d) - CHECK_TICK +/datum/SDQL2_query/New(list/tree, SU = FALSE, admin_interact = TRUE, _options = SDQL2_OPTIONS_DEFAULT, finished_qdel = FALSE) + if(IsAdminAdvancedProcCall() || !LAZYLEN(tree)) + qdel(src) + return + LAZYADD(GLOB.sdql2_queries, src) + superuser = SU + allow_admin_interact = admin_interact + query_tree = tree + options = _options + id = id_assign++ + qdel_on_finish = finished_qdel - if("select") - var/text = "" - for(var/datum/t in objs) - text += SDQL_gen_vv_href(t) - refs[REF(t)] = TRUE - CHECK_TICK - usr << browse(text, "window=SDQL-result") +/datum/SDQL2_query/Destroy() + state = SDQL2_STATE_HALTING + query_tree = null + obj_count_all = null + obj_count_eligible = null + obj_count_finished = null + select_text = null + select_refs = null + GLOB.sdql2_queries -= src + return ..() - if("update") - if("set" in query_tree) - var/list/set_list = query_tree["set"] - for(var/datum/d in objs) - SDQL_internal_vv(d, set_list) - CHECK_TICK +/datum/SDQL2_query/proc/get_query_text() + var/list/out = list() + recursive_list_print(out, query_tree) + return out.Join() - var/end_time = REALTIMEOFDAY - end_time -= start_time - return list("SDQL query results: [query_text]",\ - "SDQL query completed: [objs_all] objects selected by path, and [where_used ? objs_eligible : objs_all] objects executed on after WHERE filtering if applicable.",\ - "SDQL query took [DisplayTimeText(end_time)] to complete.") + refs +/proc/recursive_list_print(list/output = list(), list/input, datum/callback/datum_handler, datum/callback/atom_handler) + output += "\[ " + for(var/i in 1 to input.len) + var/final = i == input.len + var/key = input[i] -/proc/SDQL_qdel_datum(datum/d) - qdel(d) - -/proc/SDQL_gen_vv_href(t) - var/text = "" - text += "[REF(t)]" - if(istype(t, /atom)) - var/atom/a = t - var/turf/T = a.loc - var/turf/actual = get_turf(a) - if(istype(T)) - text += ": [t] at turf [T] [ADMIN_COORDJMP(T)]
" - else if(a.loc && istype(actual)) - text += ": [t] in [a.loc] at turf [actual] [ADMIN_COORDJMP(actual)]
" + //print the key + if(islist(key)) + recursive_list_print(output, key, datum_handler, atom_handler) + else if(is_proper_datum(key) && (datum_handler || (isatom(key) && atom_handler))) + if(isatom(key) && atom_handler) + output += atom_handler.Invoke(key) + else + output += datum_handler.Invoke(key) else - text += ": [t]
" - else - text += ": [t]
" - return text + output += "[key]" -/proc/SDQL_internal_vv(d, list/set_list) + //print the value + var/is_value = (!isnum(key) && !isnull(input[key])) + if(is_value) + var/value = input[key] + if(islist(value)) + recursive_list_print(output, value, datum_handler, atom_handler) + else if(is_proper_datum(value) && (datum_handler || (isatom(value) && atom_handler))) + if(isatom(value) && atom_handler) + output += atom_handler.Invoke(value) + else + output += datum_handler.Invoke(value) + else + output += " = [value]" + + if(!final) + output += " , " + + output += " \]" + +/datum/SDQL2_query/proc/text_state() + switch(state) + if(SDQL2_STATE_ERROR) + return "###ERROR" + if(SDQL2_STATE_IDLE) + return "####IDLE" + if(SDQL2_STATE_PRESEARCH) + return "PRESEARCH" + if(SDQL2_STATE_SEARCHING) + return "SEARCHING" + if(SDQL2_STATE_EXECUTING) + return "EXECUTING" + if(SDQL2_STATE_SWITCHING) + return "SWITCHING" + if(SDQL2_STATE_HALTING) + return "##HALTING" + +/datum/SDQL2_query/proc/generate_stat() + if(!allow_admin_interact) + return + if(!delete_click) + delete_click = new(null, "INITIALIZING", src) + if(!action_click) + action_click = new(null, "INITIALIZNG", src) + stat("[id] ", delete_click.update("DELETE QUERY | STATE : [text_state()] | ALL/ELIG/FIN \ + [islist(obj_count_all)? length(obj_count_all) : (isnull(obj_count_all)? "0" : obj_count_all)]/\ + [islist(obj_count_eligible)? length(obj_count_eligible) : (isnull(obj_count_eligible)? "0" : obj_count_eligible)]/\ + [islist(obj_count_finished)? length(obj_count_finished) : (isnull(obj_count_finished)? "0" : obj_count_finished)]")) + stat(" ", action_click.update("[SDQL2_IS_RUNNING? "HALT" : "RUN"]")) + +/datum/SDQL2_query/proc/delete_click() + admin_del(usr) + +/datum/SDQL2_query/proc/action_click() + if(SDQL2_IS_RUNNING) + admin_halt(usr) + else + admin_run(usr) + +/datum/SDQL2_query/proc/admin_halt(user = usr) + if(!SDQL2_IS_RUNNING) + return + var/msg = "[key_name(user)] has halted query #[id]" + message_admins(msg) + log_admin(msg) + state = SDQL2_STATE_HALTING + +/datum/SDQL2_query/proc/admin_run(user = usr) + if(SDQL2_IS_RUNNING) + return + var/msg = "[key_name(user)] has (re)started query #[id]" + message_admins(msg) + log_admin(msg) + ARun() + +/datum/SDQL2_query/proc/admin_del(user = usr) + var/msg = "[key_name(user)] has stopped + deleted query #[id]" + message_admins(msg) + log_admin(msg) + qdel(src) + +/datum/SDQL2_query/proc/set_option(name, value) + switch(name) + if("select") + switch(value) + if("force_nulls") + DISABLE_BITFIELD(options, SDQL2_OPTION_SELECT_OUTPUT_SKIP_NULLS) + if("proccall") + switch(value) + if("blocking") + ENABLE_BITFIELD(options, SDQL2_OPTION_BLOCKING_CALLS) + if("priority") + switch(value) + if("high") + ENABLE_BITFIELD(options, SDQL2_OPTION_HIGH_PRIORITY) + if("autogc") + switch(value) + if("keep_alive") + ENABLE_BITFIELD(options, SDQL2_OPTION_DO_NOT_AUTOGC) + +/datum/SDQL2_query/proc/ARun() + INVOKE_ASYNC(src, .proc/Run) + +/datum/SDQL2_query/proc/Run() + if(SDQL2_IS_RUNNING) + return FALSE + if(query_tree["options"]) + for(var/name in query_tree["options"]) + var/value = query_tree["options"][name] + set_option(name, value) + select_refs = list() + obj_count_all = 0 + obj_count_eligible = 0 + obj_count_finished = 0 + start_time = REALTIMEOFDAY + + state = SDQL2_STATE_PRESEARCH + var/list/search_tree = PreSearch() + SDQL2_STAGE_SWITCH_CHECK + + state = SDQL2_STATE_SEARCHING + var/list/found = Search(search_tree) + SDQL2_STAGE_SWITCH_CHECK + + state = SDQL2_STATE_EXECUTING + Execute(found) + SDQL2_STAGE_SWITCH_CHECK + + end_time = REALTIMEOFDAY + state = SDQL2_STATE_IDLE + finished = TRUE + . = TRUE + if(qdel_on_finish) + qdel(src) + +/datum/SDQL2_query/proc/PreSearch() + SDQL2_HALT_CHECK + switch(query_tree[1]) + if("explain") + SDQL_testout(query_tree["explain"]) + state = SDQL2_STATE_HALTING + return + if("call") + . = query_tree["on"] + if("select", "delete", "update") + . = query_tree[query_tree[1]] + state = SDQL2_STATE_SWITCHING + +/datum/SDQL2_query/proc/Search(list/tree) + SDQL2_HALT_CHECK + var/type = tree[1] + var/list/from = tree[2] + var/list/objs = SDQL_from_objs(from) + SDQL2_TICK_CHECK + SDQL2_HALT_CHECK + objs = SDQL_get_all(type, objs) + SDQL2_TICK_CHECK + SDQL2_HALT_CHECK + + // 1 and 2 are type and FROM. + var/i = 3 + while (i <= tree.len) + var/key = tree[i++] + var/list/expression = tree[i++] + switch (key) + if ("map") + for(var/j = 1 to objs.len) + var/x = objs[j] + objs[j] = SDQL_expression(x, expression) + SDQL2_TICK_CHECK + SDQL2_HALT_CHECK + + if ("where") + where_switched = TRUE + var/list/out = list() + obj_count_eligible = out + for(var/x in objs) + if(SDQL_expression(x, expression)) + out += x + SDQL2_TICK_CHECK + SDQL2_HALT_CHECK + objs = out + if(islist(obj_count_eligible)) + obj_count_eligible = objs.len + else + obj_count_eligible = obj_count_all + . = objs + state = SDQL2_STATE_SWITCHING + +/datum/SDQL2_query/proc/SDQL_from_objs(list/tree) + if(IsAdminAdvancedProcCall()) + if("world" in tree) + var/text = "[key_name(usr)] attempted to grab world with a procedure call to a SDQL datum." + message_admins(text) + log_admin(text) + return + if("world" in tree) + return world + return SDQL_expression(world, tree) + +/datum/SDQL2_query/proc/SDQL_get_all(type, location) + var/list/out = list() + obj_count_all = out + +// If only a single object got returned, wrap it into a list so the for loops run on it. + if(!islist(location) && location != world) + location = list(location) + + if(type == "*") + for(var/i in location) + var/datum/d = i + if(d.can_vv_get() || superuser) + out += d + SDQL2_TICK_CHECK + SDQL2_HALT_CHECK + return out + if(istext(type)) + type = text2path(type) + var/typecache = typecacheof(type) + + if(ispath(type, /mob)) + for(var/mob/d in location) + if(typecache[d.type] && (d.can_vv_get() || superuser)) + out += d + SDQL2_TICK_CHECK + SDQL2_HALT_CHECK + + else if(ispath(type, /turf)) + for(var/turf/d in location) + if(typecache[d.type] && (d.can_vv_get() || superuser)) + out += d + SDQL2_TICK_CHECK + SDQL2_HALT_CHECK + + else if(ispath(type, /obj)) + for(var/obj/d in location) + if(typecache[d.type] && (d.can_vv_get() || superuser)) + out += d + SDQL2_TICK_CHECK + SDQL2_HALT_CHECK + + else if(ispath(type, /area)) + for(var/area/d in location) + if(typecache[d.type] && (d.can_vv_get() || superuser)) + out += d + SDQL2_TICK_CHECK + SDQL2_HALT_CHECK + + else if(ispath(type, /atom)) + for(var/atom/d in location) + if(typecache[d.type] && (d.can_vv_get() || superuser)) + out += d + SDQL2_TICK_CHECK + SDQL2_HALT_CHECK + + else if(ispath(type, /datum)) + if(location == world) //snowflake for byond shortcut + for(var/datum/d) //stupid byond trick to have it not return atoms to make this less laggy + if(typecache[d.type] && (d.can_vv_get() || superuser)) + out += d + SDQL2_TICK_CHECK + SDQL2_HALT_CHECK + else + for(var/datum/d in location) + if(typecache[d.type] && (d.can_vv_get() || superuser)) + out += d + SDQL2_TICK_CHECK + SDQL2_HALT_CHECK + obj_count_all = out.len + return out + +/datum/SDQL2_query/proc/Execute(list/found) + SDQL2_HALT_CHECK + select_refs = list() + select_text = list() + switch(query_tree[1]) + if("call") + for(var/i in found) + if(!is_proper_datum(i)) + continue + world.SDQL_var(i, query_tree["call"][1], source = i, superuser) + obj_count_finished++ + SDQL2_TICK_CHECK + SDQL2_HALT_CHECK + + if("delete") + for(var/datum/d in found) + SDQL_qdel_datum(d) + obj_count_finished++ + SDQL2_TICK_CHECK + SDQL2_HALT_CHECK + + if("select") + var/list/text_list = list() + var/print_nulls = !(options & SDQL2_OPTION_SELECT_OUTPUT_SKIP_NULLS) + obj_count_finished = select_refs + for(var/i in found) + SDQL_print(i, text_list, print_nulls) + select_refs[REF(i)] = TRUE + SDQL2_TICK_CHECK + SDQL2_HALT_CHECK + select_text = text_list.Join() + + if("update") + if("set" in query_tree) + var/list/set_list = query_tree["set"] + for(var/d in found) + if(!is_proper_datum(d)) + continue + SDQL_internal_vv(d, set_list) + obj_count_finished++ + SDQL2_TICK_CHECK + SDQL2_HALT_CHECK + if(islist(obj_count_finished)) + obj_count_finished = obj_count_finished.len + state = SDQL2_STATE_SWITCHING + +/datum/SDQL2_query/proc/SDQL_print(object, list/text_list, print_nulls = TRUE) + if(is_proper_datum(object)) + text_list += "[REF(object)] : [object]" + if(istype(object, /atom)) + var/atom/A = object + var/turf/T = A.loc + var/area/a + if(istype(T)) + text_list += " at [T] [ADMIN_COORDJMP(T)]" + a = T.loc + else + var/turf/final = get_turf(T) //Recursive, hopefully? + if(istype(final)) + text_list += " at [final] [ADMIN_COORDJMP(final)]" + a = final.loc + else + text_list += " at nonexistant location" + if(a) + text_list += " in area [a]" + if(T.loc != a) + text_list += " inside [T]" + text_list += "
" + else if(islist(object)) + var/list/L = object + var/first = TRUE + text_list += "\[" + for (var/x in L) + if (!first) + text_list += ", " + first = FALSE + SDQL_print(x, text_list) + if (!isnull(x) && !isnum(x) && L[x] != null) + text_list += " -> " + SDQL_print(L[L[x]]) + text_list += "]
" + else + if(isnull(object)) + if(print_nulls) + text_list += "NULL
" + else + text_list += "[object]
" + +/datum/SDQL2_query/CanProcCall() + if(!allow_admin_interact) + return FALSE + return ..() + +/datum/SDQL2_query/vv_edit_var(var_name, var_value) + if(!allow_admin_interact) + return FALSE + if(var_name == NAMEOF(src, superuser) || var_name == NAMEOF(src, allow_admin_interact) || var_name == NAMEOF(src, query_tree)) + return FALSE + return ..() + +/datum/SDQL2_query/proc/SDQL_internal_vv(d, list/set_list) for(var/list/sets in set_list) var/datum/temp = d var/i = 0 for(var/v in sets) if(++i == sets.len) - temp.vv_edit_var(v, SDQL_expression(d, set_list[sets])) + if(superuser) + if(temp.vars.Find(v)) + temp.vars[v] = SDQL_expression(d, set_list[sets]) + else + temp.vv_edit_var(v, SDQL_expression(d, set_list[sets])) break - if(temp.vars.Find(v) && (istype(temp.vars[v], /datum))) + if(temp.vars.Find(v) && (istype(temp.vars[v], /datum) || istype(temp.vars[v], /client))) temp = temp.vars[v] else break +/datum/SDQL2_query/proc/SDQL_function_blocking(datum/object, procname, list/arguments, source) + var/list/new_args = list() + for(var/arg in arguments) + new_args[++new_args.len] = SDQL_expression(source, arg) + if(object == GLOB) // Global proc. + procname = "/proc/[procname]" + return superuser? (call(procname)(new_args)) : (WrapAdminProcCall(GLOBAL_PROC, procname, new_args)) + return superuser? (call(object, procname)(new_args)) : (WrapAdminProcCall(object, procname, new_args)) + +/datum/SDQL2_query/proc/SDQL_function_async(datum/object, procname, list/arguments, source) + set waitfor = FALSE + return SDQL_function_blocking(object, procname, arguments, source) + +/datum/SDQL2_query/proc/SDQL_expression(datum/object, list/expression, start = 1) + var/result = 0 + var/val + + for(var/i = start, i <= expression.len, i++) + var/op = "" + + if(i > start) + op = expression[i] + i++ + + var/list/ret = SDQL_value(object, expression, i) + val = ret["val"] + i = ret["i"] + + if(op != "") + switch(op) + if("+") + result = (result + val) + if("-") + result = (result - val) + if("*") + result = (result * val) + if("/") + result = (result / val) + if("&") + result = (result & val) + if("|") + result = (result | val) + if("^") + result = (result ^ val) + if("%") + result = (result % val) + if("=", "==") + result = (result == val) + if("!=", "<>") + result = (result != val) + if("<") + result = (result < val) + if("<=") + result = (result <= val) + if(">") + result = (result > val) + if(">=") + result = (result >= val) + if("and", "&&") + result = (result && val) + if("or", "||") + result = (result || val) + else + to_chat(usr, "SDQL2: Unknown op [op]") + result = null + else + result = val + + return result + +/datum/SDQL2_query/proc/SDQL_value(datum/object, list/expression, start = 1) + var/i = start + var/val = null + + if(i > expression.len) + return list("val" = null, "i" = i) + + if(istype(expression[i], /list)) + val = SDQL_expression(object, expression[i]) + + else if(expression[i] == "TRUE") + val = TRUE + + else if(expression[i] == "FALSE") + val = FALSE + + else if(expression[i] == "!") + var/list/ret = SDQL_value(object, expression, i + 1) + val = !ret["val"] + i = ret["i"] + + else if(expression[i] == "~") + var/list/ret = SDQL_value(object, expression, i + 1) + val = ~ret["val"] + i = ret["i"] + + else if(expression[i] == "-") + var/list/ret = SDQL_value(object, expression, i + 1) + val = -ret["val"] + i = ret["i"] + + else if(expression[i] == "null") + val = null + + else if(isnum(expression[i])) + val = expression[i] + + else if(ispath(expression[i])) + val = expression[i] + + else if(copytext(expression[i], 1, 2) in list("'", "\"")) + val = copytext(expression[i], 2, length(expression[i])) + + else if(expression[i] == "\[") + var/list/expressions_list = expression[++i] + val = list() + for(var/list/expression_list in expressions_list) + var/result = SDQL_expression(object, expression_list) + var/assoc + if(expressions_list[expression_list] != null) + assoc = SDQL_expression(object, expressions_list[expression_list]) + if(assoc != null) + // Need to insert the key like this to prevent duplicate keys fucking up. + var/list/dummy = list() + dummy[result] = assoc + result = dummy + val += result + else + val = world.SDQL_var(object, expression, i, object, superuser) + i = expression.len + + return list("val" = val, "i" = i) + /proc/SDQL_parse(list/query_list) var/datum/SDQL_parser/parser = new() var/list/querys = list() @@ -193,8 +911,6 @@ qdel(parser) return querys - - /proc/SDQL_testout(list/query_tree, indent = 0) var/static/whitespace = "    " var/spaces = "" @@ -220,182 +936,19 @@ else to_chat(usr, "[spaces][whitespace][query_tree[item]]") - - -/world/proc/SDQL_from_objs(list/tree) - if("world" in tree) - return src - return SDQL_expression(src, tree) - -/proc/SDQL_get_all(type, location) - var/list/out = list() - -// If only a single object got returned, wrap it into a list so the for loops run on it. - if(!islist(location) && location != world) - location = list(location) - - type = text2path(type) - var/typecache = typecacheof(type) - - if(ispath(type, /mob)) - for(var/mob/d in location) - if(typecache[d.type] && d.can_vv_get()) - out += d - CHECK_TICK - - else if(ispath(type, /turf)) - for(var/turf/d in location) - if(typecache[d.type] && d.can_vv_get()) - out += d - CHECK_TICK - - else if(ispath(type, /obj)) - for(var/obj/d in location) - if(typecache[d.type] && d.can_vv_get()) - out += d - CHECK_TICK - - else if(ispath(type, /area)) - for(var/area/d in location) - if(typecache[d.type] && d.can_vv_get()) - out += d - CHECK_TICK - - else if(ispath(type, /atom)) - for(var/atom/d in location) - if(typecache[d.type] && d.can_vv_get()) - out += d - CHECK_TICK - else if(ispath(type, /datum)) - if(location == world) //snowflake for byond shortcut - for(var/datum/d) //stupid byond trick to have it not return atoms to make this less laggy - if(typecache[d.type] && d.can_vv_get()) - out += d - CHECK_TICK - else - for(var/datum/d in location) - if(typecache[d.type] && d.can_vv_get()) - out += d - CHECK_TICK - - return out - - -/proc/SDQL_expression(datum/object, list/expression, start = 1) - var/result = 0 - var/val - - for(var/i = start, i <= expression.len, i++) - var/op = "" - - if(i > start) - op = expression[i] - i++ - - var/list/ret = SDQL_value(object, expression, i) - val = ret["val"] - i = ret["i"] - - if(op != "") - switch(op) - if("+") - result = (result + val) - if("-") - result = (result - val) - if("*") - result = (result * val) - if("/") - result = (result / val) - if("&") - result = (result & val) - if("|") - result = (result | val) - if("^") - result = (result ^ val) - if("=", "==") - result = (result == val) - if("!=", "<>") - result = (result != val) - if("<") - result = (result < val) - if("<=") - result = (result <= val) - if(">") - result = (result > val) - if(">=") - result = (result >= val) - if("and", "&&") - result = (result && val) - if("or", "||") - result = (result || val) - else - to_chat(usr, "SDQL2: Unknown op [op]") - result = null - else - result = val - - return result - -/proc/SDQL_value(datum/object, list/expression, start = 1) - var/i = start - var/val = null - - if(i > expression.len) - return list("val" = null, "i" = i) - - if(istype(expression[i], /list)) - val = SDQL_expression(object, expression[i]) - - else if(expression[i] == "!") - var/list/ret = SDQL_value(object, expression, i + 1) - val = !ret["val"] - i = ret["i"] - - else if(expression[i] == "~") - var/list/ret = SDQL_value(object, expression, i + 1) - val = ~ret["val"] - i = ret["i"] - - else if(expression[i] == "-") - var/list/ret = SDQL_value(object, expression, i + 1) - val = -ret["val"] - i = ret["i"] - - else if(expression[i] == "null") - val = null - - else if(isnum(expression[i])) - val = expression[i] - - else if(copytext(expression[i], 1, 2) in list("'", "\"")) - val = copytext(expression[i], 2, length(expression[i])) - - else if(expression[i] == "\[") - var/list/expressions_list = expression[++i] - val = list() - for(var/list/expression_list in expressions_list) - var/result = SDQL_expression(object, expression_list) - var/assoc - if(expressions_list[expression_list] != null) - assoc = SDQL_expression(object, expressions_list[expression_list]) - if(assoc != null) - // Need to insert the key like this to prevent duplicate keys fucking up. - var/list/dummy = list() - dummy[result] = assoc - result = dummy - val += result - else - val = world.SDQL_var(object, expression, i, object) - i = expression.len - - return list("val" = val, "i" = i) - -/world/proc/SDQL_var(datum/object, list/expression, start = 1, source) +//Staying as a world proc as this is called too often for changes to offset the potential IsAdminAdvancedProcCall checking overhead. +/world/proc/SDQL_var(object, list/expression, start = 1, source, superuser, datum/SDQL2_query/query) var/v + var/static/list/exclude = list("usr", "src", "marked", "global") var/long = start < expression.len - if(object == world && long && expression[start + 1] == ".") - to_chat(usr, "Sorry, but world variables are not supported at the moment.") + var/datum/D + if(is_proper_datum(object)) + D = object + + if (object == world && (!long || expression[start + 1] == ".") && !(expression[start] in exclude)) + to_chat(usr, "World variables are not allowed to be accessed. Use global.") return null + else if(expression [start] == "{" && long) if(lowertext(copytext(expression[start + 1], 1, 3)) != "0x") to_chat(usr, "Invalid pointer syntax: [expression[start + 1]]") @@ -405,12 +958,13 @@ to_chat(usr, "Invalid pointer: [expression[start + 1]]") return null start++ - else if((!long || expression[start + 1] == ".") && (expression[start] in object.vars)) - if(object.can_vv_get(expression[start])) - v = object.vars[expression[start]] + long = start < expression.len + else if(D != null && (!long || expression[start + 1] == ".") && (expression[start] in D.vars)) + if(D.can_vv_get(expression[start]) || superuser) + v = D.vars[expression[start]] else v = "SECRET" - else if(long && expression[start + 1] == ":" && hascall(object, expression[start])) + else if(D != null && long && expression[start + 1] == ":" && hascall(D, expression[start])) v = expression[start] else if(!long || expression[start + 1] == ".") switch(expression[start]) @@ -427,38 +981,70 @@ v = world if("global") v = GLOB + if("MC") + v = Master + if("FS") + v = Failsafe + if("CFG") + v = config + //Subsystem switches + if("SSgarbage") + v = SSgarbage + if("SSmachines") + v = SSmachines + if("SSobj") + v = SSobj + if("SSresearch") + v = SSresearch + if("SSprojectiles") + v = SSprojectiles + if("SSfastprocess") + v = SSfastprocess + if("SSticker") + v = SSticker + if("SStimer") + v = SStimer + if("SSradiation") + v = SSradiation + if("SSnpcpool") + v = SSnpcpool + if("SSmobs") + v = SSmobs + if("SSmood") + v = SSmood + if("SSquirks") + v = SSquirks + if("SSwet_floors") + v = SSwet_floors + if("SSshuttle") + v = SSshuttle + if("SSmapping") + v = SSmapping + if("SSevents") + v = SSevents + //End else return null else if(object == GLOB) // Shitty ass hack kill me. v = expression[start] if(long) if(expression[start + 1] == ".") - return SDQL_var(v, expression[start + 2], source = source) + return SDQL_var(v, expression[start + 2], source = source, superuser = superuser, query = query) else if(expression[start + 1] == ":") - return SDQL_function(object, v, expression[start + 2], source) + return (query.options & SDQL2_OPTION_BLOCKING_CALLS)? query.SDQL_function_async(object, v, expression[start + 2], source) : query.SDQL_function_blocking(object, v, expression[start + 2], source) else if(expression[start + 1] == "\[" && islist(v)) var/list/L = v - var/index = SDQL_expression(source, expression[start + 2]) + var/index = query.SDQL_expression(source, expression[start + 2]) if(isnum(index) && (!ISINTEGER(index) || L.len < index)) to_chat(usr, "Invalid list index: [index]") return null return L[index] return v -/proc/SDQL_function(var/datum/object, var/procname, var/list/arguments, source) - set waitfor = FALSE - var/list/new_args = list() - for(var/arg in arguments) - new_args += SDQL_expression(source, arg) - if(object == GLOB) // Global proc. - procname = "/proc/[procname]" - return WrapAdminProcCall(GLOBAL_PROC, procname, new_args) - return WrapAdminProcCall(object, procname, new_args) - /proc/SDQL2_tokenize(query_text) var/list/whitespace = list(" ", "\n", "\t") - var/list/single = list("(", ")", ",", "+", "-", ".", ";", "{", "}", "\[", "]", ":") + var/list/single = list("(", ")", ",", "+", "-", ".", "\[", "]", "{", "}", ";", ":") var/list/multi = list( "=" = list("", "="), "<" = list("", "=", ">"), @@ -560,3 +1146,20 @@ if(word != "") query_list += word return query_list + +/proc/is_proper_datum(thing) + return istype(thing, /datum) || istype(thing, /client) + +/obj/effect/statclick/SDQL2_delete/Click() + var/datum/SDQL2_query/Q = target + Q.delete_click() + +/obj/effect/statclick/SDQL2_action/Click() + var/datum/SDQL2_query/Q = target + Q.action_click() + +/obj/effect/statclick/SDQL2_VV_all + name = "VIEW VARIABLES" + +/obj/effect/statclick/SDQL2_VV_all/Click() + usr.client.debug_variables(GLOB.sdql2_queries) \ No newline at end of file diff --git a/code/modules/admin/verbs/SDQL2/SDQL_2_parser.dm b/code/modules/admin/verbs/SDQL2/SDQL_2_parser.dm index 1bd95bc239..fb857bfb02 100644 --- a/code/modules/admin/verbs/SDQL2/SDQL_2_parser.dm +++ b/code/modules/admin/verbs/SDQL2/SDQL_2_parser.dm @@ -7,36 +7,34 @@ // // query : select_query | delete_query | update_query | call_query | explain // explain : 'EXPLAIN' query +// select_query : 'SELECT' object_selectors +// delete_query : 'DELETE' object_selectors +// update_query : 'UPDATE' object_selectors 'SET' assignments +// call_query : 'CALL' variable 'ON' object_selectors // Note here: 'variable' does function calls. This simplifies parsing. // -// select_query : 'SELECT' select_list [('FROM' | 'IN') from_list] ['WHERE' bool_expression] -// delete_query : 'DELETE' select_list [('FROM' | 'IN') from_list] ['WHERE' bool_expression] -// update_query : 'UPDATE' select_list [('FROM' | 'IN') from_list] 'SET' assignments ['WHERE' bool_expression] -// call_query : 'CALL' call_function ['ON' select_list [('FROM' | 'IN') from_list] ['WHERE' bool_expression]] +// select_item : '*' | object_type // -// select_list : select_item [',' select_list] -// select_item : '*' | select_function | object_type -// select_function : count_function -// count_function : 'COUNT' '(' '*' ')' | 'COUNT' '(' object_types ')' +// object_selectors : select_item [('FROM' | 'IN') from_item] [modifier_list] +// modifier_list : ('WHERE' bool_expression | 'MAP' expression) [modifier_list] // -// from_list : from_item [',' from_list] -// from_item : 'world' | object_type +// from_item : 'world' | expression // -// call_function : ['(' [arguments] ')'] +// call_function : '(' [arguments] ')' // arguments : expression [',' arguments] // -// object_type : | string +// object_type : // -// assignments : assignment, [',' assignments] +// assignments : assignment [',' assignments] // assignment : '=' expression -// variable : | '.' variable +// variable : | '.' variable | '[' ']' | '[' ']' '.' variable // // bool_expression : expression comparitor expression [bool_operator bool_expression] // expression : ( unary_expression | '(' expression ')' | value ) [binary_operator expression] // unary_expression : unary_operator ( unary_expression | value | '(' expression ')' ) // comparitor : '=' | '==' | '!=' | '<>' | '<' | '<=' | '>' | '>=' -// value : variable | string | number | 'null' +// value : variable | string | number | 'null' | object_type // unary_operator : '!' | '-' | '~' -// binary_operator : comparitor | '+' | '-' | '/' | '*' | '&' | '|' | '^' +// binary_operator : comparitor | '+' | '-' | '/' | '*' | '&' | '|' | '^' | '%' // bool_operator : 'AND' | '&&' | 'OR' | '||' // // string : ''' ''' | '"' '"' @@ -51,10 +49,9 @@ var/list/query var/list/tree - var/list/select_functions = list("count") var/list/boolean_operators = list("and", "or", "&&", "||") var/list/unary_operators = list("!", "-", "~") - var/list/binary_operators = list("+", "-", "/", "*", "&", "|", "^") + var/list/binary_operators = list("+", "-", "/", "*", "&", "|", "^", "%") var/list/comparitors = list("=", "==", "!=", "<>", "<", "<=", ">", ">=") /datum/SDQL_parser/New(query_list) @@ -62,12 +59,12 @@ /datum/SDQL_parser/proc/parse_error(error_message) error = 1 - to_chat(usr, "SQDL2 Parsing Error: [error_message]") + to_chat(usr, "SQDL2 Parsing Error: [error_message]") return query.len + 1 /datum/SDQL_parser/proc/parse() tree = list() - query(1, tree) + query_options(1, tree) if(error) return list() @@ -91,354 +88,546 @@ /datum/SDQL_parser/proc/tokenl(i) return lowertext(token(i)) +/datum/SDQL_parser/proc/query_options(i, list/node) + var/list/options = list() + if(tokenl(i) == "using") + i = option_assignment(i + 1, node, options) + query(i, node) + node["options"] = options + +//option_assignment: query_option '=' define +/datum/SDQL_parser/proc/option_assignment(var/i, var/list/node, var/list/assignment_list = list()) + var/type = tokenl(i) + if(!(type in SDQL2_VALID_OPTION_TYPES)) + parse_error("Invalid option type: [type]") + if(!(token(i + 1) == "=")) + parse_error("Invalid option assignment symbol: [token(i + 1)]") + var/val = tokenl(i + 2) + if(!(val in SDQL2_VALID_OPTION_VALUES)) + parse_error("Invalid optoin value: [val]") + assignment_list[type] = val + return (i + 3) + +//option_assignments: option_assignment, [',' option_assignments] +/datum/SDQL_parser/proc/option_assignments(i, list/node) + i = option_assignment(i, node) + + if(token(i) == ",") + i = option_assignments(i + 1, node) + + return i //query: select_query | delete_query | update_query /datum/SDQL_parser/proc/query(i, list/node) query_type = tokenl(i) + switch(query_type) if("select") select_query(i, node) + if("delete") delete_query(i, node) + if("update") update_query(i, node) + if("call") call_query(i, node) + if("explain") node += "explain" node["explain"] = list() query(i + 1, node["explain"]) -// select_query: 'SELECT' select_list [('FROM' | 'IN') from_list] ['WHERE' bool_expression] +// select_query: 'SELECT' object_selectors /datum/SDQL_parser/proc/select_query(i, list/node) var/list/select = list() - i = select_list(i + 1, select) - node += "select" + i = object_selectors(i + 1, select) + node["select"] = select - selectors(i, node) return i -//delete_query: 'DELETE' select_list [('FROM' | 'IN') from_list] ['WHERE' bool_expression] + +//delete_query: 'DELETE' object_selectors /datum/SDQL_parser/proc/delete_query(i, list/node) var/list/select = list() - i = select_list(i + 1, select) - node += "delete" + i = object_selectors(i + 1, select) + node["delete"] = select - selectors(i, node) + return i -//update_query: 'UPDATE' select_list [('FROM' | 'IN') from_list] 'SET' assignments ['WHERE' bool_expression] + +//update_query: 'UPDATE' object_selectors 'SET' assignments /datum/SDQL_parser/proc/update_query(i, list/node) var/list/select = list() - i = select_list(i + 1, select) - node += "update" + i = object_selectors(i + 1, select) + node["update"] = select + if(tokenl(i) != "set") i = parse_error("UPDATE has misplaced SET") + var/list/set_assignments = list() i = assignments(i + 1, set_assignments) - node += "set" + node["set"] = set_assignments - selectors(i, node) + return i -//call_query: 'CALL' call_function ['ON' select_list [('FROM' | 'IN') from_list] ['WHERE' bool_expression]] + +//call_query: 'CALL' call_function ['ON' object_selectors] /datum/SDQL_parser/proc/call_query(i, list/node) var/list/func = list() i = variable(i + 1, func) // Yes technically does anything variable() matches but I don't care, if admins fuck up this badly then they shouldn't be allowed near SDQL. - node += "call" + node["call"] = func + if(tokenl(i) != "on") - return i + return parse_error("You need to specify what to call ON.") + var/list/select = list() - i = select_list(i + 1, select) - node += "on" + i = object_selectors(i + 1, select) + node["on"] = select - selectors(i, node) + return i -//select_list: select_item [',' select_list] +// object_selectors: select_item [('FROM' | 'IN') from_item] [modifier_list] +/datum/SDQL_parser/proc/object_selectors(i, list/node) + i = select_item(i, node) + + if (tokenl(i) == "from" || tokenl(i) == "in") + i++ + var/list/from = list() + i = from_item(i, from) + node[++node.len] = from + + else + node[++node.len] = list("world") + + i = modifier_list(i, node) + return i + +// modifier_list: ('WHERE' bool_expression | 'MAP' expression) [modifier_list] +/datum/SDQL_parser/proc/modifier_list(i, list/node) + while (TRUE) + if (tokenl(i) == "where") + i++ + node += "where" + var/list/expr = list() + i = bool_expression(i, expr) + node[++node.len] = expr + + else if (tokenl(i) == "map") + i++ + node += "map" + var/list/expr = list() + i = expression(i, expr) + node[++node.len] = expr + + else + return i + +//select_list:select_item [',' select_list] /datum/SDQL_parser/proc/select_list(i, list/node) i = select_item(i, node) + if(token(i) == ",") i = select_list(i + 1, node) + return i //assignments: assignment, [',' assignments] /datum/SDQL_parser/proc/assignments(i, list/node) i = assignment(i, node) + if(token(i) == ",") i = assignments(i + 1, node) + return i + //select_item: '*' | select_function | object_type /datum/SDQL_parser/proc/select_item(i, list/node) - if(token(i) == "*") + if (token(i) == "*") node += "*" i++ - else if(tokenl(i) in select_functions) - i = select_function(i, node) - else + + else if (copytext(token(i), 1, 2) == "/") i = object_type(i, node) + + else + i = parse_error("Expected '*' or type path for select item") + return i // Standardized method for handling the IN/FROM and WHERE options. /datum/SDQL_parser/proc/selectors(i, list/node) while (token(i)) var/tok = tokenl(i) - if(tok in list("from", "in")) + if (tok in list("from", "in")) var/list/from = list() i = from_item(i + 1, from) + node["from"] = from continue - if(tok == "where") + + if (tok == "where") var/list/where = list() i = bool_expression(i + 1, where) + node["where"] = where continue + parse_error("Expected either FROM, IN or WHERE token, found [token(i)] instead.") return i + 1 - if(!node.Find("from")) + + if (!node.Find("from")) node["from"] = list("world") + return i -//from_item: 'world' | object_type +//from_item: 'world' | expression /datum/SDQL_parser/proc/from_item(i, list/node) if(token(i) == "world") node += "world" i++ + else i = expression(i, node) + return i + //bool_expression: expression [bool_operator bool_expression] /datum/SDQL_parser/proc/bool_expression(i, list/node) + var/list/bool = list() i = expression(i, bool) + node[++node.len] = bool + if(tokenl(i) in boolean_operators) i = bool_operator(i, node) i = bool_expression(i, node) + return i + //assignment: '=' expression /datum/SDQL_parser/proc/assignment(var/i, var/list/node, var/list/assignment_list = list()) assignment_list += token(i) + if(token(i + 1) == ".") i = assignment(i + 2, node, assignment_list) + else if(token(i + 1) == "=") var/exp_list = list() node[assignment_list] = exp_list + i = expression(i + 2, exp_list) + else parse_error("Assignment expected, but no = found") + return i -//variable: | '.' variable + +//variable: | '.' variable | '[' ']' | '[' ']' '.' variable /datum/SDQL_parser/proc/variable(i, list/node) var/list/L = list(token(i)) node[++node.len] = L + if(token(i) == "{") - L += token(i+1) + L += token(i + 1) i += 2 + if(token(i) != "}") parse_error("Missing } at end of pointer.") + if(token(i + 1) == ".") L += "." i = variable(i + 2, L) - else if(token(i + 1) == "(") // OH BOY PROC + + else if (token(i + 1) == "(") // OH BOY PROC var/list/arguments = list() i = call_function(i, null, arguments) L += ":" L[++L.len] = arguments - else if(token(i + 1) == "\[") + + else if (token(i + 1) == "\[") var/list/expression = list() i = expression(i + 2, expression) if (token(i) != "]") parse_error("Missing ] at the end of list access.") + L += "\[" L[++L.len] = expression i++ + else i++ + return i + +//object_type: +/datum/SDQL_parser/proc/object_type(i, list/node) + + if (copytext(token(i), 1, 2) != "/") + return parse_error("Expected type, but it didn't begin with /") + + var/path = text2path(token(i)) + if (path == null) + return parse_error("Nonexistant type path: [token(i)]") + + node += path + + return i + 1 + + +//comparitor: '=' | '==' | '!=' | '<>' | '<' | '<=' | '>' | '>=' +/datum/SDQL_parser/proc/comparitor(i, list/node) + + if(token(i) in list("=", "==", "!=", "<>", "<", "<=", ">", ">=")) + node += token(i) + + else + parse_error("Unknown comparitor [token(i)]") + + return i + 1 + + +//bool_operator: 'AND' | '&&' | 'OR' | '||' +/datum/SDQL_parser/proc/bool_operator(i, list/node) + + if(tokenl(i) in list("and", "or", "&&", "||")) + node += token(i) + + else + parse_error("Unknown comparitor [token(i)]") + + return i + 1 + + +//string: ''' ''' | '"' '"' +/datum/SDQL_parser/proc/string(i, list/node) + + if(copytext(token(i), 1, 2) in list("'", "\"")) + node += token(i) + + else + parse_error("Expected string but found '[token(i)]'") + + return i + 1 + //array: '[' expression, expression, ... ']' -/datum/SDQL_parser/proc/array(i, list/node) +/datum/SDQL_parser/proc/array(var/i, var/list/node) + // Arrays get turned into this: list("[", list(exp_1a = exp_1b, ...), ...), "[" is to mark the next node as an array. if(copytext(token(i), 1, 2) != "\[") parse_error("Expected an array but found '[token(i)]'") return i + 1 - node += token(i) + + node += token(i) // Add the "[" + var/list/expression_list = list() + i++ if(token(i) != "]") var/list/temp_expression_list = list() var/tok do tok = token(i) - if(tok == "," || tok == ":") - if(temp_expression_list == null) + if (tok == "," || tok == ":") + if (temp_expression_list == null) parse_error("Found ',' or ':' without expression in an array.") return i + 1 + expression_list[++expression_list.len] = temp_expression_list temp_expression_list = null - if(tok == ":") + if (tok == ":") temp_expression_list = list() i = expression(i + 1, temp_expression_list) expression_list[expression_list[expression_list.len]] = temp_expression_list temp_expression_list = null tok = token(i) - if(tok != ",") - if(tok == "]") + if (tok != ",") + if (tok == "]") break + parse_error("Expected ',' or ']' after array assoc value, but found '[token(i)]'") return i + + i++ continue + temp_expression_list = list() i = expression(i, temp_expression_list) + + // Ok, what the fuck BYOND? + // Not having these lines here causes the parser to die + // on an error saying that list/token() doesn't exist as a proc. + // These lines prevent that. + // I assume the compiler/VM is shitting itself and swapping out some variables internally? + // While throwing in debug logging it disappeared + // And these 3 lines prevent it from happening while being quiet. + // So.. it works. + // Don't touch it. + var/whatthefuck = i + whatthefuck = src.type + whatthefuck = whatthefuck + while(token(i) && token(i) != "]") - if(temp_expression_list) + + if (temp_expression_list) expression_list[++expression_list.len] = temp_expression_list + node[++node.len] = expression_list - return i + 1 -//object_type: | string -/datum/SDQL_parser/proc/object_type(i, list/node) - if(copytext(token(i), 1, 2) == "/") - node += token(i) - else - i = string(i, node) - return i + 1 - -//comparitor: '=' | '==' | '!=' | '<>' | '<' | '<=' | '>' | '>=' -/datum/SDQL_parser/proc/comparitor(i, list/node) - if(token(i) in list("=", "==", "!=", "<>", "<", "<=", ">", ">=")) - node += token(i) - else - parse_error("Unknown comparitor [token(i)]") - return i + 1 - -//bool_operator: 'AND' | '&&' | 'OR' | '||' -/datum/SDQL_parser/proc/bool_operator(i, list/node) - if(tokenl(i) in list("and", "or", "&&", "||")) - node += token(i) - else - parse_error("Unknown comparitor [token(i)]") - return i + 1 - -//string: ''' ''' | '"' '"' -/datum/SDQL_parser/proc/string(i, list/node) - if(copytext(token(i), 1, 2) in list("'", "\"")) - node += token(i) - else - parse_error("Expected string but found '[token(i)]'") return i + 1 //call_function: ['(' [arguments] ')'] /datum/SDQL_parser/proc/call_function(i, list/node, list/arguments) if(length(tokenl(i))) var/procname = "" - if(token(i) == "global" && token(i+1) == ".") + if(tokenl(i) == "global" && token(i + 1) == ".") // Global proc. i += 2 procname = "global." node += procname + token(i++) if(token(i) != "(") parse_error("Expected ( but found '[token(i)]'") + else if(token(i + 1) != ")") - var/list/expression_list_temp = list() + var/list/temp_expression_list = list() do - i = expression(i + 1, expression_list_temp) + i = expression(i + 1, temp_expression_list) if(token(i) == ",") - arguments[++arguments.len] = expression_list_temp - expression_list_temp = list() + arguments[++arguments.len] = temp_expression_list + temp_expression_list = list() continue + while(token(i) && token(i) != ")") - arguments[++arguments.len] = expression_list_temp + + arguments[++arguments.len] = temp_expression_list // The code this is copy pasted from won't be executed when it's the last param, this fixes that. else i++ else parse_error("Expected a function but found nothing") return i + 1 -//select_function: count_function -/datum/SDQL_parser/proc/select_function(i, list/node) - parse_error("Sorry, function calls aren't available yet") - return i //expression: ( unary_expression | '(' expression ')' | value ) [binary_operator expression] /datum/SDQL_parser/proc/expression(i, list/node) + if(token(i) in unary_operators) i = unary_expression(i, node) + else if(token(i) == "(") var/list/expr = list() + i = expression(i + 1, expr) + if(token(i) != ")") parse_error("Missing ) at end of expression.") + else i++ + node[++node.len] = expr + else i = value(i, node) + if(token(i) in binary_operators) i = binary_operator(i, node) i = expression(i, node) + else if(token(i) in comparitors) i = binary_operator(i, node) + var/list/rhs = list() i = expression(i, rhs) + node[++node.len] = rhs + + return i + //unary_expression: unary_operator ( unary_expression | value | '(' expression ')' ) /datum/SDQL_parser/proc/unary_expression(i, list/node) + if(token(i) in unary_operators) var/list/unary_exp = list() + unary_exp += token(i) i++ + if(token(i) in unary_operators) i = unary_expression(i, unary_exp) + else if(token(i) == "(") var/list/expr = list() + i = expression(i + 1, expr) + if(token(i) != ")") parse_error("Missing ) at end of expression.") + else i++ + unary_exp[++unary_exp.len] = expr + else i = value(i, unary_exp) + node[++node.len] = unary_exp + + else parse_error("Expected unary operator but found '[token(i)]'") + return i -//binary_operator: comparitor | '+' | '-' | '/' | '*' | '&' | '|' | '^' + +//binary_operator: comparitor | '+' | '-' | '/' | '*' | '&' | '|' | '^' | '%' /datum/SDQL_parser/proc/binary_operator(i, list/node) + if(token(i) in (binary_operators + comparitors)) node += token(i) + else parse_error("Unknown binary operator [token(i)]") + return i + 1 -//value: variable | string | number | 'null' + +//value: variable | string | number | 'null' | object_type /datum/SDQL_parser/proc/value(i, list/node) if(token(i) == "null") node += "null" i++ - else if(lowertext(copytext(token(i),1,3)) == "0x" && isnum(hex2num(copytext(token(i),3)))) - node += hex2num(copytext(token(i),3)) + + else if(lowertext(copytext(token(i), 1, 3)) == "0x" && isnum(hex2num(copytext(token(i), 3)))) + node += hex2num(copytext(token(i), 3)) i++ + else if(isnum(text2num(token(i)))) node += text2num(token(i)) i++ + else if(copytext(token(i), 1, 2) in list("'", "\"")) i = string(i, node) + else if(copytext(token(i), 1, 2) == "\[") // Start a list. i = array(i, node) + else if(copytext(token(i), 1, 2) == "/") + i = object_type(i, node) else i = variable(i, node) - return i -/*EXPLAIN SELECT * WHERE 42 = 6 * 9 OR val = - 5 == 7*/ + return i \ No newline at end of file diff --git a/code/modules/admin/verbs/SDQL2/SDQL_2_wrappers.dm b/code/modules/admin/verbs/SDQL2/SDQL_2_wrappers.dm index 56faa1f6c4..a984e40fee 100644 --- a/code/modules/admin/verbs/SDQL2/SDQL_2_wrappers.dm +++ b/code/modules/admin/verbs/SDQL2/SDQL_2_wrappers.dm @@ -48,6 +48,12 @@ /proc/_image(icon, loc, icon_state, layer, dir) return image(icon, loc, icon_state, layer, dir) +/proc/_istype(object, type) + return istype(object, type) + +/proc/_ispath(path, type) + return ispath(path, type) + /proc/_length(E) return length(E) @@ -208,4 +214,3 @@ /proc/_step_away(ref, trg, max) step_away(ref, trg, max) - diff --git a/code/modules/mob/mob.dm b/code/modules/mob/mob.dm index a563aef86a..79215ff5f5 100644 --- a/code/modules/mob/mob.dm +++ b/code/modules/mob/mob.dm @@ -564,7 +564,12 @@ GLOB.cameranet.stat_entry() if(statpanel("Tickets")) GLOB.ahelp_tickets.stat_entry() - + if(length(GLOB.sdql2_queries)) + if(statpanel("SDQL2")) + stat("Access Global SDQL2 List", GLOB.sdql2_vv_statobj) + for(var/i in GLOB.sdql2_queries) + var/datum/SDQL2_query/Q = i + Q.generate_stat() if(listed_turf && client) if(!TurfAdjacent(listed_turf)) listed_turf = null