Guida [Release] mysql_direct_query, get_table_postfix, mysql_escape_string scritte in c lua

Stato
Discussione chiusa ad ulteriori risposte.

martysama0134

Utente Platinum
9 Gennaio 2009
3,446
80
1,704
1,107
Ultima modifica da un moderatore:
Intro:
Questo codice può esesere usato sia in lib scritte decentemente che nei source di game e creerà le seguenti funzioni nell'environment del lua:
mysql_direct_query, get_table_postfix, mysql_escape_string

mysql_direct_query ritorna due valori: il primo contiene il conteggio delle "affected rows" della query (valido per select, insert, update e così via) e il secondo, una table contenente tutti i valori ritornati. (vuota se nulla)

Il field type verrà auto-rilevato, il che significa:
  • Un campo numerico verrà pushato come lua number
  • Un campo BLOB verrà pushato come table byte per byte
  • Un campo NULL verrà pushato come nil (non mostrato quando si itera)
  • I restanti verranno pushati a mo' di stringhe (tipo stringhe ed altri tipi di dati non catalogati da me)

Esempio: (Usa CTRL+Q e CTRL+K su notepad++ per de/commentare velocemente)
Codice:
-- local res1, res2 = mysql_direct_query("select player.name, player.level from player.player limit 2;")
local res1, res2 = mysql_direct_query("select * from player.player limit 1;")
-- local res1, res2 = mysql_direct_query("select name, skill_level from player.player limit 1;")
syschat(res2[1].name)
-- syschat(string.format("count(%d)", res1))
-- for i1=1, res1 do
	-- syschat(string.format("\tindex(%d)", i1))
	-- syschat(string.format("\t\tname(%s), level(%d)", res2[i1].name, res2[i1].level))
-- end
-- local res1, res2 = mysql_direct_query("select * from player.guild_comment;")
-- local res1, res2 = mysql_direct_query("INSERT INTO `guild_comment` (`id`) VALUES ('1');")
-- local res1, res2 = mysql_direct_query("INSERT INTO `guild_comment` (`guild_id`, `name`, `notice`, `content`, `time`) VALUES ('1', 'martytest', '0', 'blabla', NOW());")
syschat(string.format("count(%d)", res1))
for num1, str1 in ipairs(res2) do
	syschat(string.format("\tindex(%d)", num1))
	for key1, val1 in pairs(str1) do
		-- syschat(string.format("\t\tkey(%s), value(%s)", key1, val1))
		-- syschat(string.format("\t\tkey(%s), value(%s) type(%s)", key1, val1, type(val1)))
		if (type(val1)=="table") then
			syschat(string.format("\t\tkey(%s), size(%d), type(%s)", key1, table.getn(val1), type(val1)))
			-- syschat(string.format("\t\tkey(%s), type(%s)", key1, type(val1)))
			-- for num2, str2 in ipairs(val1) do
				-- syschat(string.format("\t\t\tidx(%s), value(%s), type(%s)", num2, str2, type(num2)))
			-- end
			-- syschat(string.format("\t\tkey(%s), value(%s), type(%s)", key1, table.concat(val1, ", "), type(val1))) --client will crash
		else
			syschat(string.format("\t\tkey(%s), value(%s), type(%s)", key1, val1, type(val1)))
		end
	end
end

-- syschat(mysql_escape_string("abyy"))
-- syschat(mysql_escape_string("'schure'")) --\'schure\'
syschat(mysql_escape_string("'\"lewd'\"")) --\'\"lewd\'\"
-- syschat(mysql_escape_string("`\n``\t`"))
syschat(mysql_escape_string([["aww'omg"<?'3]])) --\"aww\'omg\"<?\'3

How To:

Copia ciò su questlua_global.cpp fuori dal namespace quest:
Codice:
#include "db.h"
Copiale su questlua_global.cpp dentro il namespace quest:
Codice:
	int _get_table_postfix(lua_State* L)
	{
		lua_pushstring(L, get_table_postfix());
		return 1;
	}

	int _mysql_direct_query(lua_State* L)
	{
		// char szQuery[1024];

		if (!lua_isstring(L, 1))
			return 0;
		// strncpy(szQuery, lua_tostring(L, 1), sizeof(szQuery));

		int i=0, m=1;
		MYSQL_ROW row;
		MYSQL_FIELD * field;
		MYSQL_RES * result;
		// SQLMsg * pMsg = DBManager::instance().DirectQuery(szQuery);
		std::auto_ptr<SQLMsg> pMsg(DBManager::instance().DirectQuery(lua_tostring(L, 1)));
		if (pMsg.get())
		{
			// ret1 (number of affected rows)
			lua_pushnumber(L, pMsg->Get()->uiAffectedRows); //-1 if error such as duplicate occurs (-2147483648)
			// ret2 (table of affected rows)
			lua_newtable(L);
			if ((result = pMsg->Get()->pSQLResult) &&
					!(pMsg->Get()->uiAffectedRows == 0 || pMsg->Get()->uiAffectedRows == (uint32_t)-1))
			{
				// LPCHARACTER ch = CQuestManager::instance().GetCurrentCharacterPtr();
				// ch->ChatPacket(CHAT_TYPE_INFO, "<%s> Retrieved %u fields\n", __FUNCTION__, mysql_num_fields(result));
				// ch->ChatPacket(CHAT_TYPE_INFO, "<%s> Retrieved %u rows\n", __FUNCTION__, mysql_num_rows(result));
				// ch->ChatPacket(CHAT_TYPE_INFO, "<%s> Affected %u rows\n", __FUNCTION__, pMsg->Get()->uiAffectedRows);
				// ch->ChatPacket(CHAT_TYPE_INFO, "<%s> Num %u rows\n", __FUNCTION__, pMsg->Get()->uiNumRows);

				while((row = mysql_fetch_row(result)))
				{
					lua_pushnumber(L, m);
					lua_newtable(L);
					while((field = mysql_fetch_field(result)))
					{
						lua_pushstring(L, field->name);
						if (!(field->flags & NOT_NULL_FLAG) && (row[i]==NULL))
						{
							// lua_pushstring(L, "NULL");
							lua_pushnil(L);
						}
						else if (IS_NUM(field->type))
						{
							double val = NAN;
							lua_pushnumber(L, (sscanf(row[i],"%lf",&val)==1)?val:NAN);
						}
						else if (field->type == MYSQL_TYPE_BLOB)
						{
							lua_newtable(L);
							for (DWORD iBlob=0; iBlob < field->max_length; iBlob++)
							{
								lua_pushnumber(L, row[i][iBlob]);
								lua_rawseti(L, -2, iBlob+1);
							}
						}
						else
							lua_pushstring(L, row[i]);
						// LPCHARACTER ch = CQuestManager::instance().GetCurrentCharacterPtr();
						// ch->ChatPacket(CHAT_TYPE_INFO, "<%s> Retrieved %d flag %s for %s\n", __FUNCTION__, field->type, field->name, row[i]?row[i]:"NULL");
						lua_rawset(L, -3);
						i++;
					}
					mysql_field_seek(result, 0);
					i=0;

					lua_rawset(L, -3);
					m++;
				}
			}
		}
		else {lua_pushnumber(L, 0); lua_newtable(L);}

		// delete pMsg;
		return 2;
	}

	int _mysql_escape_string(lua_State* L)
	{
		char szQuery[1024] = {0};

		if (!lua_isstring(L, 1))
			return 0;
		// mysql_escape_string(szQuery, lua_tostring(L, 1), sizeof(szQuery));
		DBManager::instance().EscapeString(szQuery, sizeof(szQuery), lua_tostring(L, 1), strlen(lua_tostring(L, 1)));
		lua_pushstring(L, szQuery);
		return 1;
	}

Dentro global_functions, incollate:
Codice:
			{	"get_table_postfix",			_get_table_postfix				},
			{	"mysql_direct_query",			_mysql_direct_query				},
			{	"mysql_escape_string",			_mysql_escape_string			},

Da sapere:



Altro:
1) Non sarai rallentato da questa feature (fin quando crei query decenti)
2) Non incorrerai in problemi di sicurezza se escaperai (con la funzione lua che ho creato) cose potenzialmente pericolose (come gli input() inviati dai player che interagiscono direttamente con la query)
3) Almeno, evita di dare un grant all al mysql account usato dal game/db (select, insert, update, delete sono più che sufficienti)

Note:
1) Il primo valore ritornato dalla direct_query function potrebbe contenere valori come -2147483648 (duplicate entry) e 4294967295 (syntax error) quando avviene un errore.
2) Il limite massimo di una query nel game secondo l'implementazione standard della ymir è di 4096 caratteri e % deve essere "escapizzato" con %% (va_arg issue)

Semplice esempio di classifica:
Codice:
local query=[[
SELECT player.player.name as m_name, player.player.level as m_level, player.player.exp as m_exp
FROM player.player, account.account
WHERE
	player.player.name not like '[%%]%%' AND
	date_sub(now(), interval 1 day) < player.player.last_play AND
	player.player.account_id=account.account.id AND
	account.account.status='OK'
ORDER BY player.player.level DESC, player.player.exp DESC, player.player.name ASC
LIMIT 10;
]]
local res1, res2 = mysql_direct_query(query)
say_title("Top "..res1.." players.")
for num1, str1 in ipairs(res2) do
	say_reward(num1.."\t"..str1.m_name.."\t"..str1.m_level.."\t"..str1.m_exp)
end
v6ybJTl.png
 
Rispetto ad execute_query di BlackYuko noto (con piacere) che tu restituisci il type adatto :)
Volevo sapere in termini di prestazioni che differenza c'è tra la tua e la sua.
Comunque sia grazie della condivisione.
Ora che noto 1 po la sintassi della tua, mi sembra più comoda, visto che non devo specificare il database.
 
Volevo sapere in termini di prestazioni che differenza c'è tra la tua e la sua.
Il workaround in lua scrive il codice su un file e poi lo parsa a mo' di table (quindi c'è parecchia perdita di prestazioni con quel i/o), senza considerare che funziona tramite os.execute, un qualcosa di esterno al tutto.
L'implementazione in c lua (senza considerare gli smart pointer del c++ ahah) è più diretta e non deve fare tutto quel lavoro extra.
È nativo e non un qualcosa di "esterno", quindi, in termini di prestazioni, può essere decine e decine di volte più rapido del workaround.
Ovviamente, il codice funziona solo via lib o modificando direttamente i source, quindi non è un qualcosa di minimale aggiungibile quando vuoi in qualsiasi rev via questlib.
 
  • Mi piace
Reazioni: stefano9876
Stato
Discussione chiusa ad ulteriori risposte.