From b5df2dfb0763fe263c0412e96d94e19aa66621b9 Mon Sep 17 00:00:00 2001 From: David Anderson <dvander@alliedmods.net> Date: Tue, 14 Aug 2007 05:42:44 +0000 Subject: [PATCH] schema change (ugh) normalized immunity properly --HG-- extra : convert_revision : svn%3A39bc706e-5318-0410-9160-8a85361fbb7c/trunk%401332 --- configs/sql-init-scripts/admins-mysql.sql | 7 +- configs/sql-init-scripts/admins-sqlite.sq3 | Bin 13312 -> 14336 bytes configs/sql-init-scripts/admins-sqlite.sql | 9 +- plugins/admin-sql-prefetch.sp | 259 ++++++++------------- plugins/admin-sql-threaded.sp | 154 ++++++------ plugins/sql-admin-manager.sp | 45 ++-- translations/sqladmins.phrases.txt | 5 + 7 files changed, 221 insertions(+), 258 deletions(-) diff --git a/configs/sql-init-scripts/admins-mysql.sql b/configs/sql-init-scripts/admins-mysql.sql index c8e06d34..12c79ed5 100644 --- a/configs/sql-init-scripts/admins-mysql.sql +++ b/configs/sql-init-scripts/admins-mysql.sql @@ -14,10 +14,15 @@ CREATE TABLE sm_groups ( immunity enum('none','all','default') NOT NULL, flags varchar(30) NOT NULL, name varchar(120) NOT NULL, - groups_immune varchar(255), PRIMARY KEY (id) ); +CREATE TABLE sm_group_immunity ( + group_id int(10) unsigned NOT NULL, + other_id int(10) unsigned NOT NULL, + PRIMARY KEY (group_id, other_id) +); + CREATE TABLE sm_group_overrides ( group_id int(10) unsigned NOT NULL, type enum('command','group') NOT NULL, diff --git a/configs/sql-init-scripts/admins-sqlite.sq3 b/configs/sql-init-scripts/admins-sqlite.sq3 index 2883fd10060345631e3d453f64200109e3503c95..2b01c1c7aee47c8f5c8ed369003e21fa1e9cf9f2 100644 GIT binary patch delta 430 zcmZq3XegK<Ey%`z3fMO4TxMouS;WJ!NPuON0E++*3&SG@hDS`SOySJ_6B{onH7PK% zi_6P1w(yrECgr3S=f)?d<Ywj-!x>DI52}hyzNPq@DS&BmkYa#RV<|ION3$hNM|x3y zX#q$Fguya-uF91VF3m<g7Itxcea0qk6ov7bxw)lznI)Al4R8Tg=O9<d5Lbl|M<*Xw z1q^8gjmdvi6$J82GE$4;GgA~i{X$&bU4tesRF~2R+YA)cQGiKna%u7c1B0DGoPl{S zQxlUdV<Mvlkn^0O(U)uT6jdEYUZBqwvU0PD2kTBgt1dmcNll)a&zy7eNp&+8p2kRy z$?w!u7`Z14t8Za*W@i%*Hw3FXuBObw(PY6k`Gk@IBm3r`O6-hmj;w6rq52?cC8$H7 OUW_-y@CDl<0RaHArGv!) delta 337 zcmZoDXvml#EvUu-0Zc%O5r~yH>Re_v6oZO_C|)4ufnshT%>~4qK+J)J**6QaTwtEW z!NJ7*5y*VS%)`%EoEx8*lAD=V%(#h%MTCo)F@S+FfN>Gi(T$Bxj7$L{li8I5l$sPo z*~R7M8C&>E5|eUL;d<Z<mdz}RjEu}p7GjfI)eI&#D2q=%qxMmRmuV3L8$&e%(=w*l z%*PogG9@xpPi#!*Y7&+Ko7Y@AIZ;V`vYU$F<W8j{$~s&M3h71pr3J<DnYp>8d8rCz ziABj7iA5Slrly))nvK3vlciL27`Z3At1M)dW)lzAovf!SJ()vQo*m?O7H9Fv_G(Tn hY>kl;lT%bxCT~>}oIF!?36r_xWGz)QMvg@SJOJS(Oxged diff --git a/configs/sql-init-scripts/admins-sqlite.sql b/configs/sql-init-scripts/admins-sqlite.sql index 1c0e2795..2a608548 100644 --- a/configs/sql-init-scripts/admins-sqlite.sql +++ b/configs/sql-init-scripts/admins-sqlite.sql @@ -12,8 +12,13 @@ CREATE TABLE sm_groups ( id INTEGER PRIMARY KEY AUTOINCREMENT, immunity varchar(16) NOT NULL CHECK(immunity IN ('none', 'default', 'global', 'all')), flags varchar(30) NOT NULL, - name varchar(120) NOT NULL, - groups_immune varchar(255) + name varchar(120) NOT NULL +); + +CREATE TABLE sm_group_immunity ( + group_id INTEGER NOT NULL, + other_id INTEGER NOT NULL, + PRIMARY KEY (group_id, other_id) ); CREATE TABLE sm_group_overrides ( diff --git a/plugins/admin-sql-prefetch.sp b/plugins/admin-sql-prefetch.sp index 4f2ad902..a5789bf2 100644 --- a/plugins/admin-sql-prefetch.sp +++ b/plugins/admin-sql-prefetch.sp @@ -101,7 +101,7 @@ FetchUsers(Handle:db) decl String:query[255], String:error[255]; new Handle:hQuery, Handle:hGroupQuery; - Format(query, sizeof(query), "SELECT id, authtype, identity, passwd, flags, name FROM sm_admins"); + Format(query, sizeof(query), "SELECT id, authtype, identity, password, flags, name FROM sm_admins"); if ((hQuery = SQL_Query(db, query)) == INVALID_HANDLE) { SQL_GetError(db, error, sizeof(error)); @@ -185,7 +185,7 @@ FetchGroups(Handle:db) decl String:query[255]; new Handle:hQuery; - Format(query, sizeof(query), "SELECT id, immunity, flags, name, groups_immune FROM sm_groups"); + Format(query, sizeof(query), "SELECT immunity, flags, name FROM sm_groups"); if ((hQuery = SQL_Query(db, query)) == INVALID_HANDLE) { @@ -196,24 +196,18 @@ FetchGroups(Handle:db) return; } - /* We cache basic group info so we can do reverse lookups */ - new Handle:groups = CreateArray(3); - /* Now start fetching groups */ decl String:immunity[16]; decl String:flags[32]; decl String:name[128]; - decl String:grp_immunity[256]; while (SQL_FetchRow(hQuery)) { - new id = SQL_FetchInt(hQuery, 0); - SQL_FetchString(hQuery, 1, immunity, sizeof(immunity)); - SQL_FetchString(hQuery, 2, flags, sizeof(flags)); - SQL_FetchString(hQuery, 3, name, sizeof(name)); - SQL_FetchString(hQuery, 4, grp_immunity, sizeof(grp_immunity)); + SQL_FetchString(hQuery, 0, immunity, sizeof(immunity)); + SQL_FetchString(hQuery, 1, flags, sizeof(flags)); + SQL_FetchString(hQuery, 2, name, sizeof(name)); #if defined _DEBUG - PrintToServer("Adding group (%d, %s, %s, %s, %s)", id, immunity, flags, name, grp_immunity); + PrintToServer("Adding group (%s, %s, %s)", immunity, flags, name); #endif /* Find or create the group */ @@ -245,108 +239,100 @@ FetchGroups(Handle:db) } else if (StrEqual(immunity, "global")) { SetAdmGroupImmunity(gid, Immunity_Global, true); } - - new Handle:immunity_list = UTIL_ExplodeNumberString(grp_immunity); - - /* Now, save all this for later */ - decl data[3]; - data[0] = id; - data[1] = _:gid; - data[2] = _:immunity_list; - - PushArrayArray(groups, data); } CloseHandle(hQuery); - /* Second pass - resolve immunity and group overrides */ - new num_groups = GetArraySize(groups); - for (new i=0; i<num_groups; i++) + /** + * Get immunity in a big lump. This is a nasty query but it gets the job done. + */ + new len = 0; + len += Format(query[len], sizeof(query)-len, "SELECT g1.name, g2.name FROM sm_group_immunity gi"); + len += Format(query[len], sizeof(query)-len, " LEFT JOIN sm_groups g1 ON g1.id = gi.group_id "); + len += Format(query[len], sizeof(query)-len, " LEFT JOIN sm_groups g2 ON g2.id = gi.other_id"); + + if ((hQuery = SQL_Query(db, query)) == INVALID_HANDLE) { - decl data[3]; - GetArrayArray(groups, i, data); - - new id = data[0]; - new GroupId:gid = GroupId:data[1]; - new Handle:immunity_list = Handle:data[2]; - - /* Query to get per-group override list */ - Format(query, - sizeof(query), - "SELECT type, name, access FROM sm_group_overrides WHERE group_id = %d", - id); - - /* Fetch the override query results */ - if ((hQuery = SQL_Query(db, query)) == INVALID_HANDLE) - { - decl String:error[255]; - SQL_GetError(db, error, sizeof(error)); - LogError("FetchOverrides() query failed: %s", query); - LogError("Query error: %s", error); - } else { - decl String:type[16]; - decl String:cmd[64]; - decl String:access[16]; - while (SQL_FetchRow(hQuery)) - { - SQL_FetchString(hQuery, 0, type, sizeof(type)); - SQL_FetchString(hQuery, 1, cmd, sizeof(cmd)); - SQL_FetchString(hQuery, 2, access, sizeof(access)); - - new OverrideType:o_type = Override_Command; - if (StrEqual(type, "group")) - { - o_type = Override_CommandGroup; - } - - new OverrideRule:o_rule = Command_Deny; - if (StrEqual(access, "allow")) - { - o_rule = Command_Allow; - } - - #if defined _DEBUG - PrintToServer("AddAdmGroupCmdOverride(%d, %s, %d, %d)", gid, cmd, o_type, o_rule); - #endif - - AddAdmGroupCmdOverride(gid, cmd, o_type, o_rule); - } - CloseHandle(hQuery); - } - - /* Lastly, resolve the immunity list if any */ - new immunity_count; - if (immunity_list != INVALID_HANDLE - && ((immunity_count = GetArraySize(immunity_list)) > 0)) - { - for (new j=0; j<immunity_count; j++) - { - new other_id = GetArrayCell(immunity_list, j); - - if (other_id == id) - { - continue; - } - - /* See if we can find this other ID */ - new GroupId:other_gid; - if ((other_gid = UTIL_FindGroupInCache(groups, other_id)) != INVALID_GROUP_ID) - { - #if defined _DEBUG - PrintToServer("SetAdmGroupImmuneFrom(%d, %d)", gid, other_gid); - #endif - SetAdmGroupImmuneFrom(gid, other_gid); - } - } - } - - if (immunity_list != INVALID_HANDLE) - { - CloseHandle(immunity_list); - } + decl String:error[255]; + SQL_GetError(db, error, sizeof(error)); + LogError("FetchGroups() query failed: %s", query); + LogError("Query error: %s", error); + return; } - CloseHandle(groups); + while (SQL_FetchRow(hQuery)) + { + decl String:group1[80]; + decl String:group2[80]; + new GroupId:gid1, GroupId:gid2; + + SQL_FetchString(hQuery, 0, group1, sizeof(group1)); + SQL_FetchString(hQuery, 1, group2, sizeof(group2)); + + if (((gid1 = FindAdmGroup(group1)) == INVALID_GROUP_ID) + || (gid2 = FindAdmGroup(group2)) == INVALID_GROUP_ID) + { + continue; + } + + SetAdmGroupImmuneFrom(gid1, gid2); +#if defined _DEBUG + PrintToServer("SetAdmGroupImmuneFrom(%d, %d)", gid1, gid2); +#endif + } + + CloseHandle(hQuery); + + /** + * Fetch overrides in a lump query. + */ + Format(query, sizeof(query), "SELECT g.name, go.type, go.name, go.access FROM sm_group_overrides go LEFT JOIN sm_groups g ON go.group_id = g.id"); + + if ((hQuery = SQL_Query(db, query)) == INVALID_HANDLE) + { + decl String:error[255]; + SQL_GetError(db, error, sizeof(error)); + LogError("FetchGroups() query failed: %s", query); + LogError("Query error: %s", error); + return; + } + + decl String:type[16]; + decl String:cmd[64]; + decl String:access[16]; + while (SQL_FetchRow(hQuery)) + { + SQL_FetchString(hQuery, 0, name, sizeof(name)); + SQL_FetchString(hQuery, 1, type, sizeof(type)); + SQL_FetchString(hQuery, 2, cmd, sizeof(cmd)); + SQL_FetchString(hQuery, 3, access, sizeof(access)); + + new GroupId:gid; + if ((gid = FindAdmGroup(name)) == INVALID_GROUP_ID) + { + continue; + } + + new OverrideType:o_type = Override_Command; + if (StrEqual(type, "group")) + { + o_type = Override_CommandGroup; + } + + new OverrideRule:o_rule = Command_Deny; + if (StrEqual(access, "allow")) + { + o_rule = Command_Allow; + } + + #if defined _DEBUG + PrintToServer("AddAdmGroupCmdOverride(%d, %s, %d, %d)", gid, cmd, o_type, o_rule); + #endif + + AddAdmGroupCmdOverride(gid, cmd, o_type, o_rule); + } + + CloseHandle(hQuery); } FetchOverrides(Handle:db) @@ -390,62 +376,3 @@ FetchOverrides(Handle:db) CloseHandle(hQuery); } - -stock GroupId:UTIL_FindGroupInCache(Handle:group_list, id) -{ - new size = GetArraySize(group_list); - - for (new i=0; i<size; i++) - { - decl data[3]; - GetArrayArray(group_list, i, data); - - if (data[0] == id) - { - return GroupId:data[1]; - } - } - - return INVALID_GROUP_ID; -} - -/** - * Breaks a comma-delimited string into a list of numbers, returning a new - * dynamic array. - * - * @param text The string to split. - * @return A new array Handle which must be closed, or - * INVALID_HANDLE on no strings found. - */ -stock Handle:UTIL_ExplodeNumberString(const String:text[]) -{ - new Handle:array = INVALID_HANDLE; - decl String:buffer[32]; - new reloc_idx, idx; - - while ((idx = SplitString(text[reloc_idx], ",", buffer, sizeof(buffer))) != -1) - { - reloc_idx += idx; - if (text[reloc_idx] == '\0') - { - break; - } - if (array == INVALID_HANDLE) - { - array = CreateArray(); - } - PushArrayCell(array, StringToInt(buffer)); - } - - if (text[reloc_idx] != '\0') - { - if (array == INVALID_HANDLE) - { - array = CreateArray(); - } - PushArrayCell(array, StringToInt(text[reloc_idx])); - } - - return array; -} - diff --git a/plugins/admin-sql-threaded.sp b/plugins/admin-sql-threaded.sp index 0b7ff19c..d3bce734 100644 --- a/plugins/admin-sql-threaded.sp +++ b/plugins/admin-sql-threaded.sp @@ -53,8 +53,8 @@ public Plugin:myinfo = * was a 100% success in the fetch. This is so we can potentially implement * connection retries in the future. * - * 4) Sequence numbers for the admin cache are ignored except for being - * non-zero, which means players in-game should be re-checked for admin \ + * 4) Sequence numbers for the user cache are ignored except for being + * non-zero, which means players in-game should be re-checked for admin * powers. */ @@ -525,6 +525,64 @@ FetchUsersWeCan(Handle:db) RebuildCachePart[_:AdminCache_Admins] = 0; } + +public OnReceiveGroupImmunity(Handle:owner, Handle:hndl, const String:error[], any:data) +{ + new Handle:pk = Handle:data; + ResetPack(pk); + + /** + * Check if this is the latest result request. + */ + new sequence = ReadPackCell(pk); + if (RebuildCachePart[_:AdminCache_Groups] != sequence) + { + /* Discard everything, since we're out of sequence. */ + CloseHandle(pk); + return; + } + + /** + * If we need to use the results, make sure they succeeded. + */ + if (hndl == INVALID_HANDLE) + { + decl String:query[255]; + ReadPackString(pk, query, sizeof(query)); + LogError("SQL error receiving group immunity: %s", error); + LogError("Query dump: %s", query); + CloseHandle(pk); + return; + } + + /* We're done with the pack forever. */ + CloseHandle(pk); + + while (SQL_FetchRow(hndl)) + { + decl String:group1[80]; + decl String:group2[80]; + new GroupId:gid1, GroupId:gid2; + + SQL_FetchString(hndl, 0, group1, sizeof(group1)); + SQL_FetchString(hndl, 1, group2, sizeof(group2)); + + if (((gid1 = FindAdmGroup(group1)) == INVALID_GROUP_ID) + || (gid2 = FindAdmGroup(group2)) == INVALID_GROUP_ID) + { + continue; + } + + SetAdmGroupImmuneFrom(gid1, gid2); +#if defined _DEBUG + PrintToServer("SetAdmGroupImmuneFrom(%d, %d)", gid1, gid2); +#endif + } + + /* Clear the sequence so another connect doesn't refetch */ + RebuildCachePart[_:AdminCache_Groups] = 0; +} + public OnReceiveGroupOverrides(Handle:owner, Handle:hndl, const String:error[], any:data) { new Handle:pk = Handle:data; @@ -554,9 +612,6 @@ public OnReceiveGroupOverrides(Handle:owner, Handle:hndl, const String:error[], return; } - /* We're done with the pack forever. */ - CloseHandle(pk); - /** * Fetch the overrides. */ @@ -598,8 +653,20 @@ public OnReceiveGroupOverrides(Handle:owner, Handle:hndl, const String:error[], AddAdmGroupCmdOverride(gid, command, o_type, o_rule); } - /* Clear the sequence so another connect doesn't refetch */ - RebuildCachePart[_:AdminCache_Groups] = 0; + /** + * It's time to get the group immunity list. + */ + new len = 0; + decl String:query[256]; + len += Format(query[len], sizeof(query)-len, "SELECT g1.name, g2.name FROM sm_group_immunity gi"); + len += Format(query[len], sizeof(query)-len, " LEFT JOIN sm_groups g1 ON g1.id = gi.group_id "); + len += Format(query[len], sizeof(query)-len, " LEFT JOIN sm_groups g2 ON g2.id = gi.other_id"); + + ResetPack(pk); + WritePackCell(pk, sequence); + WritePackString(pk, query); + + SQL_TQuery(owner, OnReceiveGroupImmunity, query, pk, DBPrio_High); } public OnReceiveGroups(Handle:owner, Handle:hndl, const String:error[], any:data) @@ -631,26 +698,20 @@ public OnReceiveGroups(Handle:owner, Handle:hndl, const String:error[], any:data return; } - /* We cache basic group info so we can do reverse lookups */ - new Handle:groups = CreateArray(3); - /** * Now start fetching groups. */ decl String:immunity[16]; decl String:flags[32]; decl String:name[128]; - decl String:grp_immunity[256]; while (SQL_FetchRow(hndl)) { - new id = SQL_FetchInt(hndl, 0); - SQL_FetchString(hndl, 1, immunity, sizeof(immunity)); - SQL_FetchString(hndl, 2, flags, sizeof(flags)); - SQL_FetchString(hndl, 3, name, sizeof(name)); - SQL_FetchString(hndl, 4, grp_immunity, sizeof(grp_immunity)); + SQL_FetchString(hndl, 0, immunity, sizeof(immunity)); + SQL_FetchString(hndl, 1, flags, sizeof(flags)); + SQL_FetchString(hndl, 2, name, sizeof(name)); #if defined _DEBUG - PrintToServer("Adding group (%d, %s, %s, %s, %s)", id, immunity, flags, name, grp_immunity); + PrintToServer("Adding group (%s, %s, %s)", immunity, flags, name); #endif /* Find or create the group */ @@ -682,65 +743,10 @@ public OnReceiveGroups(Handle:owner, Handle:hndl, const String:error[], any:data } else if (StrEqual(immunity, "global")) { SetAdmGroupImmunity(gid, Immunity_Global, true); } - - new Handle:immunity_list = UTIL_ExplodeNumberString(grp_immunity); - - /* Now, save all this for later */ - decl savedata[3]; - savedata[0] = id; - savedata[1] = _:gid; - savedata[2] = _:immunity_list; - - PushArrayArray(groups, savedata); } /** - * Resolve immunity now in a second pass. - */ - new num_groups = GetArraySize(groups); - for (new i=0; i<num_groups; i++) - { - decl savedata[3]; - GetArrayArray(groups, i, savedata); - - new id = savedata[0]; - new GroupId:gid = GroupId:savedata[1]; - new Handle:immunity_list = Handle:savedata[2]; - - if (immunity_list != INVALID_HANDLE) - { - new immunity_count = GetArraySize(immunity_list); - for (new j=0; j<immunity_count; j++) - { - new other_id = GetArrayCell(immunity_list, j); - - if (other_id == id) - { - continue; - } - - /* See if we can find this other ID */ - new GroupId:other_gid; - if ((other_gid = UTIL_FindGroupInCache(groups, other_id)) != INVALID_GROUP_ID) - { -#if defined _DEBUG - PrintToServer("SetAdmGroupImmuneFrom(%d, %d)", gid, other_gid); -#endif - SetAdmGroupImmuneFrom(gid, other_gid); - } - } - /** - * We close the Handle but don't zero it in the parent array... not worth - * the effort. - */ - CloseHandle(immunity_list); - } - } - - CloseHandle(groups); - - /** - * Now we're done with both passes. It's time to get the group override list. + * It's time to get the group override list. */ decl String:query[255]; Format(query, @@ -759,7 +765,7 @@ FetchGroups(Handle:db, sequence) decl String:query[255]; new Handle:pk; - Format(query, sizeof(query), "SELECT id, immunity, flags, name, groups_immune FROM sm_groups"); + Format(query, sizeof(query), "SELECT immunity, flags, name FROM sm_groups"); pk = CreateDataPack(); WritePackCell(pk, sequence); diff --git a/plugins/sql-admin-manager.sp b/plugins/sql-admin-manager.sp index 6e4ebcc9..9f6ea876 100644 --- a/plugins/sql-admin-manager.sp +++ b/plugins/sql-admin-manager.sp @@ -227,39 +227,54 @@ public Action:Command_DelGroup(client, args) decl String:query[256]; + new Handle:hQuery; + Format(query, sizeof(query), "SELECT id FROM sm_groups WHERE name = '%s'", safe_name); + if ((hQuery = SQL_Query(db, query)) == INVALID_HANDLE) + { + return DoError(client, db, query, "Group retrieval query failed"); + } + + if (!SQL_FetchRow(hQuery)) + { + ReplyToCommand(client, "[SM] %t", "SQL Group not found"); + CloseHandle(hQuery); + CloseHandle(db); + return Plugin_Handled; + } + + new id = SQL_FetchInt(hQuery, 0); + + CloseHandle(hQuery); + /* Delete admin inheritance for this group */ - Format(query, - sizeof(query), - "DELETE FROM sm_admins_groups WHERE group_id IN (SELECT id FROM sm_groups WHERE name = '%s')", - safe_name); + Format(query, sizeof(query), "DELETE FROM sm_admins_groups WHERE group_id = %d", id); if (!SQL_FastQuery(db, query)) { return DoError(client, db, query, "Admin group deletion query failed"); } /* Delete group overrides */ - Format(query, - sizeof(query), - "DELETE FROM sm_group_overrides WHERE group_id IN (SELECT id FROM sm_groups WHERE name = '%s')", - safe_name); + Format(query, sizeof(query), "DELETE FROM sm_group_overrides WHERE group_id = %d", id); if (!SQL_FastQuery(db, query)) { return DoError(client, db, query, "Group override deletion query failed"); } + /* Delete immunity */ + Format(query, sizeof(query), "DELETE FROM sm_group_immunity WHERE group_id = %d OR other_id = %d", id, id); + if (!SQL_FastQuery(db, query)) + { + return DoError(client, db, query, "Group immunity deletion query failed"); + } + /* Finally delete the group */ - Format(query, sizeof(query), "DELETE FROM sm_groups WHERE name = '%s'", safe_name); + Format(query, sizeof(query), "DELETE FROM sm_groups WHERE id = %d", id); if (!SQL_FastQuery(db, query)) { return DoError(client, db, query, "Group deletion query failed"); } - if (SQL_GetAffectedRows(db)) - { - ReplyToCommand(client, "[SM] %t", "SQL Group deleted"); - } else { - ReplyToCommand(client, "[SM] %t", "SQL Group not found"); - } + ReplyToCommand(client, "[SM] %t", "SQL Group deleted"); CloseHandle(db); diff --git a/translations/sqladmins.phrases.txt b/translations/sqladmins.phrases.txt index 4859b6f5..67b78336 100644 --- a/translations/sqladmins.phrases.txt +++ b/translations/sqladmins.phrases.txt @@ -5,6 +5,11 @@ "en" "Auth type must be either 'steam', 'name', or 'ip'." } + "Invalid immunity" + { + "en" "Immunity must be 'none', 'default', or 'global'." + } + "SQL Admin already exists" { "en" "An administrator with the given credentials already exists."