/* SQLite Improved v0.9.7 by Slice Changelog: 2015-07-13: * Update for 0.3.7 R2. * All crash issues should be solved now, including the previous one that happened only on some servers. 2014-10-08: * Fix crash when using stmt_fetch_row on an empty result (only affects Linux servers). 2013-10-07: * Display errors from db_query. Works only on Windows. 2012-07-23: * Fix problem with persistent databases (hopefully). 2012-07-21: * Fix crash happening on Linux related to NULL values in db_free_result. 2012-07-15: * Improvements to persistent databases. 2012-06-15: * Fix compiler crash when db_query isn't used. 2012-05-27: * Always throw warnings when invalid results are given to SQLitei functions. * Even more improvements to stability! * GetAmxBase is now updated with a JIT compatible version (all credits to Zeex). * All the default DB functions (which are hooked by SQLitei) are now compatible with the JIT plugin. * Fixed a bug in SA-MP's SQLite implementation where strings would contain signed characters instead of unsigned. Most functions work just fine with both types of strings but strcmp, for one, does not. * stmt_bind_value now deals with packed strings properly. 2012-03-22: * db_query now accept extra parameters similar to those in stmt_bind_value, allowing a very quick way to format and run queries! 2012-03-21: * Fixed a rare crash that would occur if you closed a DB while having autofreed results open. * The deprecated db_query_autofree is now removed. Simply use db_query instead. * Fixed a problem where a certain integer would cause an invalid value. * Added DB::TYPE_UINT, which inserts an unsigned integer. An unsigned integer have values between 0 and 4294967295 as opposed to -2147483648 and 2147483647. * Added helper function db_print_query. * Added db_dump_table! 2012-03-01: * db_free_result will now completely ignore invalid results (0). 2012-02-12: * Added db_attach_memory_db and db_detach_memory_db. 2012-02-11: * Added DB::TYPE_ARRAY, which allows you to insert and read arrays with statements! * Improved error handling and increased some buffer sizes. * Added db_begin_transaction, db_end_transaction, and db_set_asynchronous. * Minor improvements here and there. 2012-02-10: * A few bug fixes. * Added db_field_is_null, which returns true if the field is a true NULL value (not just an empty string). * Deprecated db_query_autofree. 2012-02-09: * Defining WP_Hash prior to inclusion is no longer needed. * db_query now has an optional third argument - enable autorelease, which is true by default. * Improved stability on freeing results: - Freeing results twice will not crash the server anymore - it will just generate a warning. - Freeing a result that will be autoreleased will remove it from the autorelease pool. * The compiler will no longer wrongfully detect recursion inside this include. * Added db_query_int and db_query_float. * Added db_get_struct_info and db_set_struct_info; used mainly internally. * Added db_exec and db_insert. * Performance improvements! 2012-02-08: * db_print_result will no longer go to the end of the result. * Added db_get_row_index, db_set_row_index, and db_rewind. 2012-02-07: * db_get_field/db_get_field_assoc will not crash anymore with NULL values! 2011-12-21: * Added two new types for stmt_bind_value: - DB::TYPE_WP_HASH: Puts a BLOB value of a whirlpool hash from the given string into the query (ex. x'FFAA4411...'). - DB::TYPE_PLAYER_NAME: Puts a player name from the ID passed. Note that DB_USE_WHIRLPOOL must be defined as true in order for DB::TYPE_WP_HASH to work. * Made some optimizations and minor bug fixes. * stmt_execute will now autofree the result unless the 3rd argument is false. * Added the preprocessor options DE_DEBUG (logs debug info) and DB_LOG_TO_CHAT (prints log messages to chat). * Improved the way results are dealt with internally to avoid crashes at all costs. * Added debug messages pretty much everywhere. 2011-12-16: * Added db_open_persistent, db_is_persistent, db_is_valid_persistent, and db_free_persistent. * Added db_query_autofree. * Added db_get_field_int and db_get_field_float. * Corrected a few SQLite natives. 2011-12-15: * Added stmt_autoclose. * Memory usage decreased significantly. * All functions now accept both packed and unpacked strings. * Minor bug fixes and optimizations. 2011-12-14: * Initial release. */ #if !defined _samp_included #error Please include a_samp before sqlitei. #endif #if defined __fmt_funcinc && defined FormatSpecifier #error Please include sqlitei before formatex. #endif #if !defined HTTP #tryinclude #endif #if !defined DB_MAX_PARAMS #define DB_MAX_PARAMS 32 #endif #if !defined DB_MAX_STATEMENTS #define DB_MAX_STATEMENTS 16 #endif #if !defined DB_MAX_STATEMENT_SIZE #define DB_MAX_STATEMENT_SIZE 1024 #endif #if !defined DB_MAX_FIELDS #define DB_MAX_FIELDS 64 #endif #if !defined DB_MAX_PERSISTENT_DATABASES #define DB_MAX_PERSISTENT_DATABASES 4 #endif #if !defined DB_USE_WHIRLPOOL #define DB_USE_WHIRLPOOL false #endif #if !defined DB_DEBUG #define DB_DEBUG false #endif #if !defined DB_DEBUG_BACKTRACE_NOTICE #define DB_DEBUG_BACKTRACE_NOTICE false #endif #if !defined DB_DEBUG_BACKTRACE_WARNING #define DB_DEBUG_BACKTRACE_WARNING false #endif #if !defined DB_DEBUG_BACKTRACE_ERROR #define DB_DEBUG_BACKTRACE_ERROR false #endif #if !defined DB_DEBUG_BACKTRACE_DEBUG #define DB_DEBUG_BACKTRACE_DEBUG false #endif #if !defined DB_LOG_TO_CHAT #define DB_LOG_TO_CHAT false #endif // "Namespace" #define DB:: DB_ // Fix some natives ("const" keyword was missing) native DB:db_open@(const szName[]) = db_open; native DBResult:db_query@(DB:db, const szQuery[]) = db_query; native db_get_field@(DBResult:dbresult, field, result[], maxlength) = db_get_field; native db_get_field_assoc@(DBResult:dbresult, const field[], result[], maxlength) = db_get_field_assoc; native db_close@(DB:db) = db_close; native db_free_result@(DBResult:dbrResult) = db_free_result; #define db_open db_open@ #define db_query%1( db_query_hook(_, #define db_close db_close_hook #define db_free_result db_free_result_hook #if DB::USE_WHIRLPOOL native DB::WP_Hash(buffer[], len, const str[]) = WP_Hash; #endif enum DB::e_SYNCHRONOUS_MODE { DB::SYNCHRONOUS_OFF, DB::SYNCHRONOUS_NORMAL, DB::SYNCHRONOUS_FULL }; enum DBDataType: { DB::TYPE_NONE, DB::TYPE_NULL, DB::TYPE_INT, DB::TYPE_INTEGER = DB::TYPE_INT, DB::TYPE_UINT, DB::TYPE_UINTEGER = DB::TYPE_UINT, DB::TYPE_FLOAT, DB::TYPE_STRING, DB::TYPE_RAW_STRING, DB::TYPE_IDENTIFIER, // Special types #if DB::USE_WHIRLPOOL DB::TYPE_WP_HASH, #endif DB::TYPE_PLAYER_NAME, DB::TYPE_PLAYER_IP, DB::TYPE_ARRAY }; #define INT: DB::TYPE_INT,QQPA: #define INTEGER: DB::TYPE_INT,QQPA: #define UINT: DB::TYPE_UINT,QQPA: #define UINTEGER: DB::TYPE_UINT,QQPA: #define FLOAT: DB::TYPE_FLOAT,QQPA: #define STRING: DB::TYPE_STRING,QQPA: #define RAW_STRING: DB::TYPE_RAW_STRING,QQPA: #define IDENTIFIER: DB::TYPE_IDENTIFIER,QQPA: #if DB::USE_WHIRLPOOL #define WP_HASH: DB::TYPE_WP_HASH,QQPA: #endif #define PLAYER_NAME: DB::TYPE_PLAYER_NAME,QQPA: #define PLAYER_IP: DB::TYPE_PLAYER_IP,QQPA: enum DB::E_STATEMENT { // The ready-to-run query. e_szQuery[DB::MAX_STATEMENT_SIZE + 1], // Parameter count e_iParams, // The parameter types. DBDataType:e_aiParamTypes[DB::MAX_PARAMS], // Position of parameters in the query string. e_aiParamPositions[DB::MAX_PARAMS], // Length of parameters in the query string. e_aiParamLengths[DB::MAX_PARAMS], // Types of bound return fields DBDataType:e_aiFieldTypes[DB::MAX_FIELDS], // Sizes of bound result fields (used only for strings currently) e_aiFieldSizes[DB::MAX_FIELDS], // Addresses of bound result fields e_aiFieldAddresses[DB::MAX_FIELDS], // The database it was created for DB:e_dbDatabase, // The result (after executing) DBResult:e_dbrResult, // How many rows were fetched from the most recent result e_iFetchedRows, // Whether or not any leftover results should be automatically freed // Note that whenever a new result is put into e_dbrResult, the previous one is freed. bool:e_bAutoFreeResult }; enum { SQLITE_LIMIT_LENGTH, SQLITE_LIMIT_SQL_LENGTH, SQLITE_LIMIT_COLUMN, SQLITE_LIMIT_EXPR_DEPTH, SQLITE_LIMIT_COMPOUND_SELECT, SQLITE_LIMIT_VDBE_OP, SQLITE_LIMIT_FUNCTION_ARG, SQLITE_LIMIT_ATTACHED, SQLITE_LIMIT_LIKE_PATTERN_LEN, SQLITE_LIMIT_VARIABLE_NUMBER, SQLITE_LIMIT_TRIGGER_DEPTH }; // SQLite 3 C-struct offsets enum e_SQLITE3 { sqlite3_pVfs [4], sqlite3_iBackends [4], sqlite3_pBackends [4], sqlite3_iFlags [4], sqlite3_iOpenFlags [4], sqlite3_iErrorCode [4], sqlite3_iErrorMask [4], bool:sqlite3_bAutoCommit [1], sqlite3_iTempStore [1], bool:sqlite3_bMallocFailed [1], sqlite3_iDefaultLockMode [1], sqlite3_iNextAutovac [1], bool:sqlite3_bSuppressErrors [1], sqlite3_iPad [2], sqlite3_iNextPagesize [4], sqlite3_iNumTables [4], sqlite3_pDefaultCollation [4], sqlite3_iLastRowid [4], sqlite3_iLastRowidUpperBytes[4], sqlite3_iMagic [4], sqlite3_iNumChanges [4], sqlite3_iNumTotalChanges [4], sqlite3_aiLimits [SQLITE_LIMIT_TRIGGER_DEPTH * 4], sqlite3_aInitInfo [12], sqlite3_iNumExtensions [4], sqlite3_pExtensions [4], sqlite3_pVdbe [4], sqlite3_iActiveVdbeCount [4], sqlite3_iWritingVdbeCount [4], sqlite3_pTraceFunc [4], sqlite3_pTraceArg [4], sqlite3_pProfilingFunc [4], sqlite3_pProfilingArg [4], sqlite3_pCommitArg [4], sqlite3_pCommitCallback [4], sqlite3_pRollbackArg [4], sqlite3_pRollbackCallback [4], sqlite3_pUpdateArg [4], sqlite3_pUpdateCallback [4], sqlite3_pWalCallback [4], sqlite3_pWalArg [4], sqlite3_pCollNeeded [4], sqlite3_pCollNeeded16 [4], sqlite3_pCollNeededArg [4], sqlite3_pError [4], sqlite3_pErrorMsg [4], sqlite3_pErrorMsg16 [4] }; enum DB::E_PERSISTENT_DB { e_szName[128 char], bool:e_bIsUsed, DB:e_dbDatabase }; static stock gs_szBuffer[8192], gs_aiCompressBuffer[3072], gs_Statements[DBStatement:DB::MAX_STATEMENTS][DB::E_STATEMENT], gs_iAutoFreeTimer = -1, gs_iAutoFreeResultsIndex = 0, DBResult:gs_adbrAutoFreeResults[1024], gs_iAutoCloseStatementsIndex = 0, DBStatement:gs_astAutoCloseStatements[1024], gs_PersistentDatabases[DB::MAX_PERSISTENT_DATABASES][DB::E_PERSISTENT_DB], gs_iClosePersistentTimer = -1, gs_iFreeStatementResultsTimer = -1, gs_szNull[1] = {0} ; const DBStatement:DB::INVALID_STATEMENT = DBStatement:-1, DBResult:DB::INVALID_RESULT = DBResult:0 ; #if !DB_DEBUG #define DB_Debug(%1)%0; #endif #if !DB_LOG_TO_CHAT #if DB_DEBUG_BACKTRACE_NOTICE #define DB_Notice(%1) print(!"SQLitei Notice: " %1),PrintAmxBacktrace() #define DB_Noticef(%1) printf("SQLitei Notice: " %1),PrintAmxBacktrace() #else #define DB_Notice(%1) print(!"SQLitei Notice: " %1) #define DB_Noticef(%1) printf("SQLitei Notice: " %1) #endif #if DB_DEBUG_BACKTRACE_WARNING #define DB_Warning(%1) print(!"SQLitei Warning: " %1),PrintAmxBacktrace() #define DB_Warningf(%1) printf("SQLitei Warning: " %1),PrintAmxBacktrace() #else #define DB_Warning(%1) print(!"SQLitei Warning: " %1) #define DB_Warningf(%1) printf("SQLitei Warning: " %1) #endif #if DB_DEBUG_BACKTRACE_ERROR #define DB_Error(%1) print(!"SQLitei Error: " %1),PrintAmxBacktrace() #define DB_Errorf(%1) printf("SQLitei Error: " %1),PrintAmxBacktrace() #else #define DB_Error(%1) print(!"SQLitei Error: " %1) #define DB_Errorf(%1) printf("SQLitei Error: " %1) #endif #if DB_DEBUG #if DB_DEBUG_BACKTRACE_DEBUG #define DB_Debug(%1) printf("SQLitei Debug: " %1),PrintAmxBacktrace() #else #define DB_Debug(%1) printf("SQLitei Debug: " %1) #endif #endif #else new gs_szLogMessageBuffer[256] ; #define DB_Notice(%1) SendClientMessageToAll(0xFFFFFFFF, "SQLitei Notice: " %1), print(!"SQLitei Notice: " %1) #define DB_Warning(%1) SendClientMessageToAll(0xEBBD17FF, "SQLitei Warning: " %1), print(!"SQLitei Warning: " %1) #define DB_Error(%1) SendClientMessageToAll(0xCC0000FF, "SQLitei Error: " %1), print(!"SQLitei Error: " %1) #define DB_Noticef(%1) format(gs_szLogMessageBuffer, sizeof(gs_szLogMessageBuffer), "SQLitei Notice: " %1), print(gs_szLogMessageBuffer), SendClientMessageToAll(0xDDDDDDFF, gs_szLogMessageBuffer) #define DB_Warningf(%1) format(gs_szLogMessageBuffer, sizeof(gs_szLogMessageBuffer), "SQLitei Warning: " %1), print(gs_szLogMessageBuffer), SendClientMessageToAll(0xEBBD17FF, gs_szLogMessageBuffer) #define DB_Errorf(%1) format(gs_szLogMessageBuffer, sizeof(gs_szLogMessageBuffer), "SQLitei Error: " %1), print(gs_szLogMessageBuffer), SendClientMessageToAll(0xCC0000FF, gs_szLogMessageBuffer) #if DB_DEBUG #define DB_Debug(%1) format(gs_szLogMessageBuffer, sizeof(gs_szLogMessageBuffer), "SQLitei Debug: " %1), print(gs_szLogMessageBuffer), SendClientMessageToAll(0xFFFFFFFF, gs_szLogMessageBuffer) #endif #endif // Has to be defined after statement functions. forward DBResult:db_query_hook(iTagOf3 = tagof(_bAutoRelease), DB:db, const szQuery[], {bool, DBDataType}:_bAutoRelease = true, {DBDataType, QQPA}:...); // forward's forward bool:db_set_row_index(DBResult:dbrResult, iRow); forward bool:db_free_result_hook(DBResult:dbrResult); forward bool:db_set_synchronous(DB:db, DB::e_SYNCHRONOUS_MODE:iValue); forward DB::funcinc(); public DB::funcinc() { strcat(gs_szBuffer, ""); strpack(gs_szBuffer, ""); ispacked(gs_szBuffer); db_get_field(DBResult:0, 0, gs_szBuffer, 0); db_query(DB:0,""); } static stock DB::CompressArray(const aiArray[], iSize = sizeof(aiArray), aiOutput[]) { new iOutputIndex = 4, iValue, iMSB, iShift ; // * 0b11000000 = Single byte, negative // * 0b10000000 = Single byte // * 0b01000000 = Multi-byte // - 0b01000000 = More bytes // - 0b11000000 = Last byte // - 0b10000000 = Unused for (new i = 0; i < iSize; i++) { // Will the value fit in one byte? iValue = aiArray[i]; if (-0b00111111 <= iValue <= 0b00111111) { // Is the value negative? if (iValue & 0x80000000) { // Set the "single byte, negative" bits on and put the value without its sign aiOutput{iOutputIndex++} = 0b11000000 | -iValue; } else { // Just put the value in with the "single byte" bit aiOutput{iOutputIndex++} = 0b10000000 | iValue; } } else { // Figure out how many bits we'll have to write iMSB = DB::FindMSB(iValue) + 1; // Make iShift a multiple of 6 (if it isn't already) if ((iShift = iMSB % 6)) aiOutput{iOutputIndex++} = 0b01000000 | (iValue >>> (iMSB - iShift) & ~(0xFFFFFFFF << iShift)); iShift = iMSB - iShift; // Write bits out left-right while ((iShift -= 6) >= 0) aiOutput{iOutputIndex++} = 0b01000000 | (iValue >>> iShift & 0b00111111); // Change the "more bytes" bits into "last byte" aiOutput{iOutputIndex - 1} |= 0b11000000; } } // Put the number of bytes we just wrote into the first cell of the output aiOutput[0] = 0x80808080 | ((iOutputIndex & 0x1FE00000) << 3) | ((iOutputIndex & 0x3FC000) << 2) | ((iOutputIndex & 0x7F80) << 1) | (iOutputIndex & 0x7F); // Make sure the bytes in the last cell are 0 aiOutput{iOutputIndex} = 0; iValue = iOutputIndex; while (++iOutputIndex % 4) aiOutput{iOutputIndex} = 0; // Return the number of bytes written (not counting the first 4) return iValue; } static stock DB::DecompressArray(const aiCompressedArray[], aiOutput[], iOutputSize = sizeof(aiOutput)) { new iBytes, iOutputIndex = 0 ; // Get the number of bytes to parse iBytes = aiCompressedArray[0]; iBytes = ((iBytes & 0x7F000000) >>> 3) | ((iBytes & 0x7F0000) >>> 2) | ((iBytes & 0x7F00) >>> 1) | (iBytes & 0x7F); for (new i = 4; i < iBytes; i++) { // Out of slots? if (iOutputIndex >= iOutputSize) { DB::Error("(DB::DecompressArray) Compressed array is larger than decompress buffer."); break; } // Single byte? if ((aiCompressedArray{i} & 0b10000000)) { // Negative? if ((aiCompressedArray{i} & 0b01000000)) aiOutput[iOutputIndex++] = -(aiCompressedArray{i} & 0b00111111); else aiOutput[iOutputIndex++] = (aiCompressedArray{i} & 0b00111111); } else { // Multi byte; read the last bits aiOutput[iOutputIndex] = aiCompressedArray{i} & 0b00111111; // Keep reading bits while shifting the value to the left do { aiOutput[iOutputIndex] <<= 6; aiOutput[iOutputIndex] |= aiCompressedArray{++i} & 0b00111111; } while ((aiCompressedArray{i} & 0b10000000) == 0); iOutputIndex++; } } return iOutputIndex; } static stock DB::memset(aArray[], iValue, iSize = sizeof(aArray)) { new iAddress ; // Store the address of the array #emit LOAD.S.pri 12 #emit STOR.S.pri iAddress // Convert the size from cells to bytes iSize *= 4; // Loop until there is nothing more to fill while (iSize > 0) { // I have to do this because the FILL instruction doesn't accept a dynamic number. if (iSize >= 4096) { #emit LOAD.S.alt iAddress #emit LOAD.S.pri iValue #emit FILL 4096 iSize -= 4096; iAddress += 4096; } else if (iSize >= 1024) { #emit LOAD.S.alt iAddress #emit LOAD.S.pri iValue #emit FILL 1024 iSize -= 1024; iAddress += 1024; } else if (iSize >= 256) { #emit LOAD.S.alt iAddress #emit LOAD.S.pri iValue #emit FILL 256 iSize -= 256; iAddress += 256; } else if (iSize >= 64) { #emit LOAD.S.alt iAddress #emit LOAD.S.pri iValue #emit FILL 64 iSize -= 64; iAddress += 64; } else if (iSize >= 16) { #emit LOAD.S.alt iAddress #emit LOAD.S.pri iValue #emit FILL 16 iSize -= 16; iAddress += 16; } else { #emit LOAD.S.alt iAddress #emit LOAD.S.pri iValue #emit FILL 4 iSize -= 4; iAddress += 4; } } // aArray is used, just not by its symbol name #pragma unused aArray return 1; } stock DB::LazyInitialize() { static bool:bIsInitialized = false ; if (bIsInitialized) return; bIsInitialized = true; } stock db_escape_string(szString[], const szEnclosing[] = "'", iSize = sizeof(szString)) { DB::LazyInitialize(); new iPos ; while (-1 != (iPos = strfind(szString, szEnclosing, _, iPos))) { strins(szString, szEnclosing, iPos, iSize); iPos += 2; } } stock bool:db_is_persistent(DB:db) { return !!(_:db & 0x80000000); } stock bool:db_is_valid_persistent(DB:db) { new iIndex = (_:db & 0x7FFFFFFF) ; if ((0 <= iIndex < sizeof(gs_PersistentDatabases)) && gs_PersistentDatabases[iIndex][e_bIsUsed]) return true; return false; } stock bool:db_is_table_exists(DB:db, const szTable[]) { new DBResult:dbrResult ; format(gs_szBuffer, sizeof(gs_szBuffer), "SELECT name FROM sqlite_master WHERE type = 'table' AND tbl_name = '%s'", szTable); dbrResult = db_query(db, gs_szBuffer, false); if (db_num_fields(dbrResult)) { db_free_result(dbrResult); return true; } db_free_result(dbrResult); return false; } stock bool:db_rewind(DBResult:dbrResult) { if (dbrResult == DB::INVALID_RESULT) { DB::Notice("(db_rewind) Invalid result given."); return false; } return db_set_row_index(dbrResult, 0); } stock bool:db_exec(DB:db, const szQuery[]) { new DBResult:dbrResult = db_query(db, szQuery, false) ; if (dbrResult) { db_free_result(dbrResult); return true; } return false; } stock db_insert(DB:db, const szQuery[]) { new DBResult:dbrResult = db_query(db, szQuery, false) ; if (dbrResult) { db_free_result(dbrResult); return db_last_insert_rowid(db); } return 0; } stock db_get_struct_info(DB:db, {_, e_SQLITE3}:iOffset) { if (db_is_persistent(db)) { if (!db_is_valid_persistent(db)) { DB::Errorf("(db_get_struct_info) Invalid persistent database given (%04x%04x).", _:db >>> 16, _:db & 0xFFFF); return 0; } new iIndex = (_:db & 0x7FFFFFFF); db = gs_PersistentDatabases[iIndex][e_dbDatabase]; if (!db) { DB::Errorf("(db_get_struct_info) Closed persistent database given (%04x%04x).", _:db >>> 16, _:db & 0xFFFF); return 0; } } new iAddress = db_get_mem_handle(db & DB:0x7FFFFFFF) - DB::GetAmxBaseRelative() + iOffset, iValue ; #emit LREF.S.pri iAddress #emit STOR.S.pri iValue return iValue; } stock db_set_struct_info(DB:db, {_, e_SQLITE3}:iOffset, iValue) { if (db_is_persistent(db)) { if (!db_is_valid_persistent(db)) { DB::Errorf("(db_set_struct_info) Invalid persistent database given (%04x%04x).", _:db >>> 16, _:db & 0xFFFF); return 0; } new iIndex = (_:db & 0x7FFFFFFF); db = gs_PersistentDatabases[iIndex][e_dbDatabase]; if (!db) { DB::Errorf("(db_set_struct_info) Closed persistent database given (%04x%04x).", _:db >>> 16, _:db & 0xFFFF); return 0; } } new iAddress = db_get_mem_handle(db & DB:0x7FFFFFFF) - DB::GetAmxBaseRelative() + iOffset ; #emit LOAD.S.pri iValue #emit SREF.S.pri iAddress } stock bool:db_set_row_index(DBResult:dbrResult, iRow) { if (dbrResult == DB::INVALID_RESULT) { DB::Notice("(db_set_row_index) Invalid result given."); return false; } if (iRow < 0 || iRow >= db_num_rows(dbrResult)) return false; new iAddress = db_get_result_mem_handle(dbrResult) - DB::GetAmxBaseRelative() + 16 ; #emit LOAD.S.pri iRow #emit SREF.S.pri iAddress return true; } stock db_get_row_index(DBResult:dbrResult) { if (dbrResult == DB::INVALID_RESULT) { DB::Notice("(db_get_row_index) Invalid result given."); return 0; } new iAddress = db_get_result_mem_handle(dbrResult) - DB::GetAmxBaseRelative() + 16 ; #emit LREF.S.pri iAddress #emit STACK 4 #emit RETN return 0; } stock DB:db_open_persistent(const szName[]) { new DB:db, iIndex = -1 ; for (new i = 0; i < sizeof(gs_PersistentDatabases); i++) { if (!gs_PersistentDatabases[i][e_bIsUsed]) { iIndex = i; break; } } if (iIndex == -1) { DB::Error("(db_open_persistent) Unable to find a free slot."); return DB:-1; } if (!(db = db_open(szName))) { DB::Error("(db_open_persistent) Unable to open the database."); return DB:-1; } gs_PersistentDatabases[iIndex][e_bIsUsed] = true; gs_PersistentDatabases[iIndex][e_dbDatabase] = db; gs_PersistentDatabases[iIndex][e_szName][0] = 0; if (gs_iClosePersistentTimer == -1) gs_iClosePersistentTimer = SetTimer("db_close_persistent", 0, false); strpack(gs_PersistentDatabases[iIndex][e_szName], szName, _:e_bIsUsed); DB::Debug("(db_open_persistent=%d) Opened new persistent DB.", iIndex); return DB:(iIndex | 0x80000000); } stock db_close_hook(DB:db) { new iIndex ; if (db_is_persistent(db)) { if (!db_is_valid_persistent(db)) { DB::Errorf("(db_close) Invalid persistent database given (%04x%04x).", _:db >>> 16, _:db & 0xFFFF); return; } iIndex = (_:db & 0x7FFFFFFF); if (gs_PersistentDatabases[iIndex][e_dbDatabase]) { db_close@(gs_PersistentDatabases[iIndex][e_dbDatabase]); gs_PersistentDatabases[iIndex][e_dbDatabase] = DB:0; DB::Debug("(db_close) Closed the DB for the persistent DB with index %d.", iIndex); } else { DB::Debug("(db_close) Would close the DB for the persistent DB with index %d, but it already is.", iIndex); } } else { db_close@(db); } } stock db_query_int(DB:db, const szQuery[], iField = 0) { new DBResult:dbrResult = db_query(db, szQuery, false) ; if (!dbrResult) { strunpack(gs_szBuffer, szQuery); DB::Warningf("(db_query_int) Query failed: \"%s\".", gs_szBuffer); return 0; } db_get_field(dbrResult, iField, gs_szBuffer, sizeof(gs_szBuffer) - 1); db_free_result(dbrResult); return strval(gs_szBuffer); } stock Float:db_query_float(DB:db, const szQuery[], iField = 0) { new DBResult:dbrResult = db_query(db, szQuery, false) ; if (!dbrResult) { strunpack(gs_szBuffer, szQuery); DB::Warningf("(db_query_float) Query failed: \"%s\".", gs_szBuffer); return 0.0; } db_get_field(dbrResult, iField, gs_szBuffer, sizeof(gs_szBuffer) - 1); db_free_result(dbrResult); return floatstr(gs_szBuffer); } stock db_is_result_freed(DBResult:dbrResult) { if (dbrResult == DB::INVALID_RESULT) { DB::Notice("(db_is_result_freed) Invalid result given."); return true; } return db_get_result_mem_handle(dbrResult) == 0; } stock bool:db_free_result_hook(DBResult:dbrResult) { if (dbrResult == DB::INVALID_RESULT) { DB::Notice("(db_free_result_hook) Invalid result given."); return false; } DB::Debug("(db_free_result) Freeing 0x%04x%04x.", _:dbrResult >>> 16, _:dbrResult & 0xFFFF); for (new i = gs_iAutoFreeResultsIndex; i--; ) { if (gs_adbrAutoFreeResults[i] == dbrResult) { gs_adbrAutoFreeResults[i] = DBResult:0; DB::Debug("(db_free_result) The result being freed was inside the autorelease pool."); } } new iFreeTestAddress = db_get_result_mem_handle(dbrResult) - DB::GetAmxBaseRelative() + 16, iData ; #emit LREF.S.pri iFreeTestAddress #emit STOR.S.pri iData if (iData != 0xFFFFFFFF) { new iResultAddress = db_get_result_mem_handle(dbrResult) - DB::GetAmxBaseRelative(), iAddress, iRows, iCols, iDataAddress, iOffset, iNullAddress ; #emit CONST.pri gs_szNull #emit STOR.S.pri iNullAddress iNullAddress += DB::GetAmxBaseRelative(); iAddress = iResultAddress; #emit LREF.S.pri iAddress #emit STOR.S.pri iRows iAddress += 4; #emit LREF.S.pri iAddress #emit STOR.S.pri iCols iAddress += 4; #emit LREF.S.pri iAddress #emit STOR.S.pri iDataAddress iDataAddress -= DB::GetAmxBaseRelative(); iOffset = (iCols + iRows * iCols) * 4 - 4; while (iOffset >= 0) { iAddress = iDataAddress + iOffset; #emit LREF.S.pri iAddress #emit STOR.S.pri iAddress if (iAddress == iNullAddress) { iAddress = iDataAddress + iOffset; #emit CONST.pri 0 #emit SREF.S.pri iAddress } iOffset -= 4; } db_free_result@(dbrResult); #emit CONST.pri 0xFFFFFFFF #emit SREF.S.pri iFreeTestAddress return true; } else { DB::Warning("(db_free_result) Attempted to free an already freed result; crash prevented."); } return false; } stock db_free_persistent(DB:db) { new iIndex ; if (!db_is_valid_persistent(db)) { DB::Errorf("(db_free_persistent) Invalid persistent database given (%04x%04x).", _:db >>> 16, _:db & 0xFFFF); return; } iIndex = (_:db & 0x7FFFFFFF); if (gs_PersistentDatabases[iIndex][e_dbDatabase]) { db_close@(gs_PersistentDatabases[iIndex][e_dbDatabase]); gs_PersistentDatabases[iIndex][e_dbDatabase] = DB:0; DB::Debug("(db_free_persistent:%d) Closed and freed the persistent DB.", iIndex); } else { DB::Debug("(db_free_persistent:%d) Freed the already closed persistent DB.", iIndex); } gs_PersistentDatabases[iIndex][e_bIsUsed] = false; } stock db_changes(DB:db) { DB::LazyInitialize(); if (!db) { DB::Error("(db_changes) Invalid database handle given."); return 0; } return db_get_struct_info(db, sqlite3_iNumChanges); } stock db_begin_transaction(DB:db) return db_exec(db, !"BEGIN"); stock db_end_transaction(DB:db) return db_exec(db, !"COMMIT"); stock db_set_asynchronous(DB:db, bool:bSet = true) { db_set_synchronous(DB:db, bSet ? DB::SYNCHRONOUS_OFF : DB::SYNCHRONOUS_FULL); } stock bool:db_set_synchronous(DB:db, DB::e_SYNCHRONOUS_MODE:iValue) { if (0 <= _:iValue <= 2) { format(gs_szBuffer, sizeof(gs_szBuffer), "PRAGMA synchronous = %d", _:iValue); return db_exec(db, gs_szBuffer); } else return false; } stock bool:db_attach_memory_db(DB:db, const szName[]) { strunpack(gs_szBuffer, szName); db_escape_string(gs_szBuffer, "\""); format(gs_szBuffer, sizeof(gs_szBuffer), "ATTACH DATABASE ':memory:' AS \"%s\"", gs_szBuffer); return db_exec(db, gs_szBuffer); } stock bool:db_detach_memory_db(DB:db, const szName[]) { strunpack(gs_szBuffer, szName); db_escape_string(gs_szBuffer, "\""); format(gs_szBuffer, sizeof(gs_szBuffer), "DETACH DATABASE \"%s\"", gs_szBuffer); return db_exec(db, gs_szBuffer); } stock db_total_changes(DB:db) { DB::LazyInitialize(); if (!db) { DB::Error("(db_changes) Invalid database handle given."); return 0; } return db_get_struct_info(db, sqlite3_iNumTotalChanges); } stock db_last_insert_rowid(DB:db) { DB::LazyInitialize(); if (!db) { DB::Error("(db_last_insert_rowid) Invalid database handle given."); return 0; } return db_get_struct_info(db, sqlite3_iLastRowid); } stock db_field_is_null(DBResult:dbrResult, iField) { if (dbrResult == DB::INVALID_RESULT) { DB::Notice("(db_field_is_null) Invalid result given."); return false; } new iAddress = db_get_result_mem_handle(dbrResult) - DB::GetAmxBaseRelative(), iCols, iCurrentRow ; iAddress += 4; #emit LREF.S.pri iAddress #emit STOR.S.pri iCols if (iField >= iCols) return true; iAddress += 8; #emit LREF.S.pri iAddress #emit STOR.S.pri iCurrentRow iAddress -= 4; #emit LREF.S.pri iAddress #emit STOR.S.pri iAddress iAddress -= DB::GetAmxBaseRelative(); iAddress += (iCols + iCols * iCurrentRow + iField) * 4; #emit LREF.S.pri iAddress #emit CONST.alt gs_szNull #emit SUB #emit STOR.S.pri iAddress iAddress -= DB::GetAmxBaseRelative(); return !iAddress; } stock db_autofree_result(DBResult:dbrResult) { DB::LazyInitialize(); if (dbrResult == DB::INVALID_RESULT) { DB::Notice("(db_autofree_result) Invalid result given."); return; } if (gs_iAutoFreeTimer == -1) gs_iAutoFreeTimer = SetTimer("db_drain_autofree_pool", 0, false); if (gs_iAutoFreeResultsIndex + 1 >= sizeof(gs_adbrAutoFreeResults)) { DB::Warning("(db_autofree_result) The autofree pool is full!"); return; } gs_adbrAutoFreeResults[gs_iAutoFreeResultsIndex] = dbrResult; DB::Debug("(db_autofree_result) Will autofree 0x%04x%04x.", _:dbrResult >>> 16, _:dbrResult & 0xFFFF); gs_iAutoFreeResultsIndex++; } stock DBStatement:db_prepare(DB:db, const szQuery[]) { DB::LazyInitialize(); new DBStatement:stStatement = DB::INVALID_STATEMENT, iPos, i, iLength ; if (!db) { DB::Error("(db_prepare) Invalid database handle given."); return DB::INVALID_STATEMENT; } // Pretty useless to prepare empty queries. if (!(iLength = strlen(szQuery))) { DB::Error("(db_prepare) Empty query."); return DB::INVALID_STATEMENT; } if (iLength char > DB::MAX_STATEMENT_SIZE) { DB::Error("(db_prepare) The query is too long. Increase DB_MAX_STATEMENT_SIZE."); return DB::INVALID_STATEMENT; } // Find an empty slot in gs_Statements. for (i = 0; i < sizeof(gs_Statements); i++) { if (!gs_Statements[DBStatement:i][e_dbDatabase]) { stStatement = DBStatement:i; break; } } if (stStatement == DB::INVALID_STATEMENT) { DB::Error("(db_prepare) Unable to find an empty slot for the statement. Increase DB_MAX_STATEMENTS."); return DB::INVALID_STATEMENT; } gs_Statements[stStatement][e_dbDatabase] = db; gs_Statements[stStatement][e_dbrResult] = DB::INVALID_RESULT; gs_Statements[stStatement][e_iFetchedRows] = 0; // Make sure no parameters are initialized. for (i = 0; i < DB::MAX_PARAMS; i++) gs_Statements[stStatement][e_aiParamTypes][i] = DB::TYPE_NONE; // Make sure no return fields are initialized. for (i = 0; i < DB::MAX_FIELDS; i++) gs_Statements[stStatement][e_aiFieldTypes][i] = DB::TYPE_NONE; iPos = -1; i = 0; // Find all parameters while (-1 != (iPos = strfind(szQuery, !"?", _, ++iPos))) { gs_Statements[stStatement][e_aiParamPositions][i] = iPos; gs_Statements[stStatement][e_aiParamLengths][i] = 1; if (++i >= DB::MAX_PARAMS) { DB::Error("(db_prepare) Parameter limit exceeded. Increase DB_MAX_PARAMS."); return DB::INVALID_STATEMENT; } } gs_Statements[stStatement][e_iParams] = i; gs_Statements[stStatement][e_szQuery][0] = 0; if (ispacked(szQuery)) { #if DB_DEBUG strunpack(gs_szBuffer, szQuery); DB::Debug("(db_prepare=%d) Preparing statement with %d params: %s", _:stStatement, i, gs_szBuffer); #endif strcat(gs_Statements[stStatement][e_szQuery], szQuery, DB::MAX_STATEMENT_SIZE); } else { DB::Debug("(db_prepare=%d) Preparing statement with %d params: %s", _:stStatement, i, szQuery); strpack(gs_Statements[stStatement][e_szQuery], szQuery, DB::MAX_STATEMENT_SIZE); } return stStatement; } stock bool:stmt_bind_value(&DBStatement:stStatement, iParam, DBDataType:iType, {Float, _}:...) { DB::LazyInitialize(); new iLengthDiff, iLength, bool:bIsPacked, iNumArgs ; #emit LOAD.S.pri 8 #emit SHR.C.pri 2 #emit STOR.S.pri iNumArgs if (stStatement == DB::INVALID_STATEMENT || !(0 <= _:stStatement < sizeof(gs_Statements))) { DB::Warningf("(stmt_bind_value) Invalid statement passed (%d).", _:stStatement); return false; } if (iParam >= gs_Statements[stStatement][e_iParams]) { DB::Warningf("(stmt_bind_value) Parameter index larger than number of parameters (%d > %d).", iParam, gs_Statements[stStatement][e_iParams]); return false; } // Fill gs_szBuffer with the new contents. gs_szBuffer[0] = 0; switch (iType) { case DB::TYPE_NULL: goto default_case; case DB::TYPE_INT: { new iArgValue = getarg(3) ; if (iArgValue == cellmin) gs_szBuffer = !"-2147483648"; else format(gs_szBuffer, sizeof(gs_szBuffer), "%d", getarg(3)); } case DB::TYPE_UINT: { new iArgValue = getarg(3) ; if (!iArgValue) { gs_szBuffer = !"0"; } else { new j = 11 ; gs_szBuffer = "00000000000"; while (iArgValue) { // gs_szBuffer[--j] #emit CONST.alt gs_szBuffer // alt = *gs_szBuffer #emit LOAD.S.pri j // pri = j #emit DEC.pri // pri -= 1 #emit STOR.S.pri j // j = pri #emit IDXADDR // pri = alt + j * 4 #emit PUSH.pri // Store for later // Now do an unsigned divide on iArgValue then use both the quotient and remainder! #emit LOAD.S.pri iArgValue // pri = iArgValue #emit CONST.alt 10 #emit UDIV // pri = iArgValue / 10; alt = iArgValue % 10 #emit STOR.S.pri iArgValue // iArgValue = pri #emit CONST.pri '0' #emit ADD // pri = '0' + (iArgValue % 10) #emit POP.alt // alt = gs_szBuffer[j] #emit STOR.I // gs_szBuffer[j] = pri } strpack(gs_szBuffer, gs_szBuffer[j]); } } case DB::TYPE_FLOAT: format(gs_szBuffer, sizeof(gs_szBuffer), "%f", getarg(3)); case DB::TYPE_STRING: { new iSize = sizeof(gs_szBuffer) - 3; //strpack(dest[], const source[], maxlength = sizeof dest) #emit PUSH.S iSize #emit PUSH.S 24 // arg 3 #emit PUSH.C gs_szBuffer #emit PUSH.C 12 #emit SYSREQ.C strpack #emit STACK 16 db_escape_string(gs_szBuffer, "'", sizeof(gs_szBuffer) - 1); strins(gs_szBuffer, !"'", 0); strcat(gs_szBuffer, !"'"); } #if DB::USE_WHIRLPOOL case DB::TYPE_WP_HASH: { strcat(gs_szBuffer, "x'"); DB::getstringarg(gs_szBuffer[2], 3, sizeof(gs_szBuffer) - 2); DB::WP_Hash(gs_szBuffer[2], sizeof(gs_szBuffer) - 2, gs_szBuffer[2]); strcat(gs_szBuffer, "'"); } #endif case DB::TYPE_PLAYER_NAME: { new iPlayer = getarg(3) ; if (!(0 <= iPlayer < MAX_PLAYERS) || !IsPlayerConnected(iPlayer)) { DB::Warningf("(stmt_bind_value) Invalid player ID passed for DB::TYPE_PLAYER_NAME (%d).", iPlayer); strcat(gs_szBuffer, !"''"); } else { gs_szBuffer[0] = '\''; GetPlayerName(iPlayer, gs_szBuffer[1], sizeof(gs_szBuffer) - 1); db_escape_string(gs_szBuffer[1], "'", sizeof(gs_szBuffer) - 1); strcat(gs_szBuffer, "'"); } } case DB::TYPE_PLAYER_IP: { new iPlayer = getarg(3) ; if (!(0 <= iPlayer < MAX_PLAYERS) || !IsPlayerConnected(iPlayer)) { DB::Warningf("(stmt_bind_value) Invalid player ID passed for DB::TYPE_PLAYER_IP (%d).", iPlayer); strcat(gs_szBuffer, !"''"); } else { gs_szBuffer[0] = '\''; GetPlayerIp(iPlayer, gs_szBuffer[1], sizeof(gs_szBuffer) - 1); db_escape_string(gs_szBuffer[1], "'", sizeof(gs_szBuffer) - 1); strcat(gs_szBuffer, "'"); } } // http://www.sqlite.org/lang_keywords.html case DB::TYPE_IDENTIFIER: { bIsPacked = !!DB::isargpacked(3); if (!bIsPacked) gs_szBuffer[0] = '"'; DB::getstringarg(gs_szBuffer[bIsPacked ? 0 : 1], 3, sizeof(gs_szBuffer) - 3); db_escape_string(gs_szBuffer[bIsPacked ? 0 : 1], "\"", sizeof(gs_szBuffer) - 1); if (bIsPacked) strins(gs_szBuffer, "\"", 0); strcat(gs_szBuffer, "\""); } case DB::TYPE_RAW_STRING: DB::getstringarg(gs_szBuffer, 3); case DB::TYPE_ARRAY: { // DB::CompressArray(const aiArray[], iSize = sizeof(aiArray), aiOutput[]) if (iNumArgs != 5) { DB::Error("(stmt_bind_value) Invalid argument count. DB::TYPE_ARRAY requires an additional argument containing the array's length."); return false; } new iCompressedSize, i ; // Push the output array #emit PUSH.C gs_aiCompressBuffer // Push the size #emit LREF.S.pri 28 // 12 + 4 cells #emit PUSH.pri // Push the input array #emit PUSH.S 24 // Push the argument count #emit PUSH.C 12 // Push the return address #emit LCTRL 6 #emit ADD.C 28 #emit PUSH.pri // Call DB::CompressArray #emit CONST.pri DB_CompressArray #emit SCTRL 6 // Store the return value #emit STOR.S.pri iCompressedSize iCompressedSize = (iCompressedSize + 3) / 4; gs_szBuffer[0] = 'x'; gs_szBuffer[1] = '\''; if ((iCompressedSize << 3) + 1 < sizeof(gs_szBuffer)) { for (i = 0; i < iCompressedSize; i++) format(gs_szBuffer[2 + (i << 3)], sizeof(gs_szBuffer) - (2 + (i << 3)), "%01x%07x", gs_aiCompressBuffer[i] >>> 28, gs_aiCompressBuffer[i] & 0x0FFFFFFF); } else { strcat(gs_szBuffer, "00"); DB::Errorf("(stmt_bind_value) Unable to compress the array; out of buffer size (has %d, needs %d).", sizeof(gs_szBuffer), (iCompressedSize << 3) + 2); return false; } strcat(gs_szBuffer, "'"); } default: { default_case: strcat(gs_szBuffer, "NULL"); } } iLength = strlen(gs_szBuffer); iLengthDiff = iLength - gs_Statements[stStatement][e_aiParamLengths][iParam]; // Adjust the position of any params after the one being modified. for (new i = iParam + 1; i < gs_Statements[stStatement][e_iParams]; i++) gs_Statements[stStatement][e_aiParamPositions][i] += iLengthDiff; // Delete the old parameter from the query. strdel(gs_Statements[stStatement][e_szQuery], gs_Statements[stStatement][e_aiParamPositions][iParam], gs_Statements[stStatement][e_aiParamPositions][iParam] + gs_Statements[stStatement][e_aiParamLengths][iParam]); // Make sure we have enough space. if ((strlen(gs_Statements[stStatement][e_szQuery]) + iLength) char > DB::MAX_STATEMENT_SIZE) { DB::Error("(stmt_bind_value) Buffer overflow. Increase DB_MAX_STATEMENT_SIZE."); stmt_close(stStatement); return false; } // Insert the new parameter. strins(gs_Statements[stStatement][e_szQuery], gs_szBuffer, gs_Statements[stStatement][e_aiParamPositions][iParam], DB::MAX_STATEMENT_SIZE); #if DB_DEBUG if (ispacked(gs_szBuffer)) strunpack(gs_szBuffer, gs_szBuffer); DB::Debug("(stmt_bind_value:%d) Inserted new value for parameter %d at %d: %s", _:stStatement, iParam, gs_Statements[stStatement][e_aiParamPositions][iParam], gs_szBuffer); #endif gs_Statements[stStatement][e_aiParamLengths][iParam] = iLength; gs_Statements[stStatement][e_aiParamTypes][iParam] = iType; return true; } stock stmt_bind_result_field(&DBStatement:stStatement, iField, DBDataType:iType, {Float, _}:...) { DB::LazyInitialize(); new iAddress, iSize, iNumArgs ; #emit LOAD.S.pri 8 #emit SHR.C.pri 2 #emit STOR.S.pri iNumArgs if (stStatement == DB::INVALID_STATEMENT || !(0 <= _:stStatement < sizeof(gs_Statements))) { DB::Warningf("(stmt_bind_result_field) Invalid statement passed (%d).", _:stStatement); return; } if (iField < 0) { DB::Errorf("(stmt_bind_result_field) Negative field index (%d).", iField); return; } switch (iType) { case DB::TYPE_STRING, DB::TYPE_RAW_STRING, DB::TYPE_IDENTIFIER, DB::TYPE_ARRAY: { if (iNumArgs != 5) { DB::Error("(stmt_bind_result_field) Invalid number of arguments passed. Strings and arrays require an additional argument containing the string size."); return; } iSize = getarg(4); } case DB::TYPE_NONE: { gs_Statements[stStatement][e_aiFieldTypes][iField] = DB::TYPE_NONE; return; } default: { if (iNumArgs != 4) { DB::Error("(stmt_bind_result_field) Invalid number of arguments passed."); return; } iSize = 1; } } if (iField >= DB::MAX_FIELDS) { DB::Warningf("(stmt_bind_result_field) Field index larger than max number of fields (%d > %d). Increase DB_MAX_FIELDS.", iField, DB::MAX_FIELDS); return; } // Without this, STOR.S.pri doesn't seem to do what it should. iAddress = 0; #emit LOAD.S.pri 24 #emit STOR.S.pri iAddress gs_Statements[stStatement][e_aiFieldTypes][iField] = iType; gs_Statements[stStatement][e_aiFieldAddresses][iField] = iAddress; gs_Statements[stStatement][e_aiFieldSizes][iField] = iSize; DB::Debug("(stmt_bind_result_field:%d) Bound result field %d (type %d) to variable 0x%04x%04x.", _:stStatement, iField, _:iType, iAddress >>> 16, iAddress & 0xFFFF); } stock bool:stmt_skip_row(&DBStatement:stStatement) { DB::LazyInitialize(); if (stStatement == DB::INVALID_STATEMENT || !(0 <= _:stStatement < sizeof(gs_Statements))) { DB::Errorf("(stmt_skip_row) Invalid statement passed (%d).", _:stStatement); return false; } if (gs_Statements[stStatement][e_dbrResult] == DB::INVALID_RESULT) { if (!gs_Statements[stStatement][e_iFetchedRows]) DB::Warning("(stmt_skip_row) Statement has no result."); return false; } gs_Statements[stStatement][e_iFetchedRows]++; if (!db_next_row(gs_Statements[stStatement][e_dbrResult])) { db_free_result(gs_Statements[stStatement][e_dbrResult]); gs_Statements[stStatement][e_dbrResult] = DB::INVALID_RESULT; DB::Debug("(stmt_skip_row:%d) Skipped row and freed result.", _:stStatement); } else { DB::Debug("(stmt_skip_row:%d) Skipped row.", _:stStatement); } return true; } stock bool:stmt_fetch_row(&DBStatement:stStatement) { DB::LazyInitialize(); if (stStatement == DB::INVALID_STATEMENT || !(0 <= _:stStatement < sizeof(gs_Statements))) { DB::Errorf("(stmt_fetch_row) Invalid statement passed (%d).", _:stStatement); return false; } if (gs_Statements[stStatement][e_dbrResult] == DB::INVALID_RESULT) { if (!gs_Statements[stStatement][e_iFetchedRows]) DB::Warning("(stmt_fetch_row) Statement has no result."); return false; } if (!stmt_rows_left(stStatement)) { DB::Debug("(stmt_fetch_row) No rows left."); return false; } if (!db_num_rows(gs_Statements[stStatement][e_dbrResult])) { DB::Debug("(stmt_fetch_row) Freed previous result."); db_free_result(gs_Statements[stStatement][e_dbrResult]); gs_Statements[stStatement][e_dbrResult] = DB::INVALID_RESULT; return false; } new iFields = db_num_fields(gs_Statements[stStatement][e_dbrResult]), iAddress, xValue, iCount ; if (iFields > DB::MAX_FIELDS) { DB::Warning("(stmt_bind_result_field) There are more fields returned than DB_MAX_FIELDS."); iFields = DB::MAX_FIELDS; } gs_Statements[stStatement][e_iFetchedRows]++; for (new iField = 0; iField < iFields; iField++) { if (gs_Statements[stStatement][e_aiFieldTypes][iField] == DB::TYPE_NONE) continue; iCount++; switch (gs_Statements[stStatement][e_aiFieldTypes][iField]) { case DB::TYPE_NONE, DB::TYPE_NULL: continue; case DB::TYPE_INT, DB::TYPE_UINT, DB::TYPE_FLOAT: { db_get_field(gs_Statements[stStatement][e_dbrResult], iField, gs_szBuffer, sizeof(gs_szBuffer) - 1); iAddress = gs_Statements[stStatement][e_aiFieldAddresses][iField]; xValue = (gs_Statements[stStatement][e_aiFieldTypes][iField] != DB::TYPE_FLOAT) ? strval(gs_szBuffer) : _:floatstr(gs_szBuffer); #emit LOAD.S.pri xValue #emit SREF.S.pri iAddress } // Assumes the field is a string containing a player name. // Gives the ID of a connected play with that name, otherwise INVALID_PLAYER_ID. case DB::TYPE_PLAYER_NAME: { static s_szName[MAX_PLAYER_NAME] ; xValue = INVALID_PLAYER_ID; for (new i = 0, l = GetMaxPlayers(); i < l; i++) { if (!IsPlayerConnected(i)) continue; GetPlayerName(i, s_szName, sizeof(s_szName)); db_get_field(gs_Statements[stStatement][e_dbrResult], iField, gs_szBuffer, sizeof(gs_szBuffer) - 1); if (!strcmp(gs_szBuffer, s_szName, true)) { xValue = i; break; } } iAddress = gs_Statements[stStatement][e_aiFieldAddresses][iField]; #emit LOAD.S.pri xValue #emit SREF.S.pri iAddress } case DB::TYPE_STRING, DB::TYPE_RAW_STRING, DB::TYPE_IDENTIFIER: { new DBResult:dbrResult, iSize ; static const sc_szFormatString[] = "%s" ; iAddress = gs_Statements[stStatement][e_aiFieldAddresses][iField]; dbrResult = gs_Statements[stStatement][e_dbrResult]; iSize = gs_Statements[stStatement][e_aiFieldSizes][iField]; #emit PUSH.S iSize #emit PUSH.S iAddress #emit PUSH.S iField #emit PUSH.S dbrResult #emit PUSH.C 16 #emit SYSREQ.C db_get_field #emit STACK 20 // Fix a bug with UTF-8 characters // For example, 'รถ' would become 0xFFFFFFC3 instead of 0xC3 #emit PUSH.S iAddress #emit PUSH.C sc_szFormatString #emit PUSH.S iSize #emit PUSH.S iAddress #emit PUSH.C 16 #emit SYSREQ.C format #emit STACK 20 } case DB::TYPE_ARRAY: { // DecompressArray(const aiCompressedArray[], aiOutput[], iOutputSize = sizeof(aiOutput)) db_get_field(gs_Statements[stStatement][e_dbrResult], iField, gs_szBuffer, sizeof(gs_szBuffer) - 1); format(gs_szBuffer, sizeof(gs_szBuffer), "%s", gs_szBuffer); strpack(gs_szBuffer, gs_szBuffer); new iOutputSize = gs_Statements[stStatement][e_aiFieldSizes][iField], iDecompressedCells ; iAddress = gs_Statements[stStatement][e_aiFieldAddresses][iField]; // Push the output array size #emit PUSH.S iOutputSize // Push the output array #emit PUSH.S iAddress // Push the input array #emit PUSH.C gs_szBuffer // Push the argument count #emit PUSH.C 12 // Push the return address #emit LCTRL 6 #emit ADD.C 28 #emit PUSH.pri // Call DB::DecompressArray #emit CONST.pri DB_DecompressArray #emit SCTRL 6 // Store the return address #emit STOR.S.pri iDecompressedCells // If there are more cells in the array, fill them with 0 if (iOutputSize > iDecompressedCells) { DB::Noticef("(stmt_fetch_row:%d) The array fetched from the DB is smaller than the destination array (%d > %d); the remaining slots with will be filled with 0.", _:stStatement, iOutputSize, iDecompressedCells); iAddress += iDecompressedCells * 4-4; iOutputSize = iOutputSize - iDecompressedCells; // DB::memset(aArray[], iValue, iSize = sizeof(aArray)) #emit PUSH.S iOutputSize #emit PUSH.C 0 #emit PUSH.S iAddress #emit PUSH.C 12 #emit LCTRL 6 #emit ADD.C 28 #emit PUSH.pri #emit CONST.pri DB_memset #emit SCTRL 6 } } } } if (!db_next_row(gs_Statements[stStatement][e_dbrResult])) { db_free_result(gs_Statements[stStatement][e_dbrResult]); gs_Statements[stStatement][e_dbrResult] = DB::INVALID_RESULT; DB::Debug("(stmt_fetch_row:%d) Fetched %d fields and freed the result.", _:stStatement, iCount); } else { DB::Debug("(stmt_fetch_row:%d) Fetched %d fields.", _:stStatement, iCount); } return true; } stock stmt_rows_left(&DBStatement:stStatement) { DB::LazyInitialize(); if (stStatement == DB::INVALID_STATEMENT || !(0 <= _:stStatement < sizeof(gs_Statements))) { DB::Errorf("(stmt_rows_left) Invalid statement passed (%d).", _:stStatement); return 0; } if (gs_Statements[stStatement][e_dbrResult] == DB::INVALID_RESULT) { if (!gs_Statements[stStatement][e_iFetchedRows]) DB::Warning("(stmt_rows_left) Statement has no result."); return 0; } return max(0, db_num_rows(gs_Statements[stStatement][e_dbrResult]) - gs_Statements[stStatement][e_iFetchedRows]); } stock bool:stmt_execute(&DBStatement:stStatement, bool:bStoreResult = true, bool:bAutoFreeResult = true) { DB::LazyInitialize(); if (stStatement == DB::INVALID_STATEMENT || !(0 <= _:stStatement < sizeof(gs_Statements))) { DB::Errorf("(stmt_execute) Invalid statement passed (%d).", _:stStatement); return false; } if (!gs_Statements[stStatement][e_dbDatabase]) { DB::Errorf("(stmt_execute) Uninitialized statement passed (%d).", _:stStatement); return false; } // Make sure all parameters have been set. for (new i = 0; i < gs_Statements[stStatement][e_iParams]; i++) { if (gs_Statements[stStatement][e_aiParamTypes][i] == DB::TYPE_NONE) { DB::Errorf("(stmt_execute) Uninitialized parameter in statement (%d).", i); return false; } } // If old results are left, free them. if (gs_Statements[stStatement][e_dbrResult] != DB::INVALID_RESULT) { db_free_result(gs_Statements[stStatement][e_dbrResult]); gs_Statements[stStatement][e_dbrResult] = DB::INVALID_RESULT; } DB::Debug("(stmt_execute:%d) Executing statement.", _:stStatement); new DBResult:dbrResult = db_query(gs_Statements[stStatement][e_dbDatabase], gs_Statements[stStatement][e_szQuery], false) ; gs_Statements[stStatement][e_iFetchedRows] = 0; gs_Statements[stStatement][e_bAutoFreeResult] = bAutoFreeResult; if (dbrResult == DB::INVALID_RESULT) return false; if (!bStoreResult) db_free_result(dbrResult); else { gs_Statements[stStatement][e_dbrResult] = dbrResult; if (bAutoFreeResult && gs_iFreeStatementResultsTimer == -1) gs_iFreeStatementResultsTimer = SetTimer("db_free_stmt_results", 1, false); } return true; } stock stmt_free_result(&DBStatement:stStatement) { DB::LazyInitialize(); if (stStatement == DB::INVALID_STATEMENT || !(0 <= _:stStatement < sizeof(gs_Statements))) { DB::Noticef("(stmt_free_result) Invalid statement passed (%d).", _:stStatement); return; } gs_Statements[stStatement][e_iFetchedRows] = 0; if (gs_Statements[stStatement][e_dbrResult] != DB::INVALID_RESULT) { db_free_result(gs_Statements[stStatement][e_dbrResult]); gs_Statements[stStatement][e_dbrResult] = DB::INVALID_RESULT; DB::Debug("(stmt_free_result:%d) Freed result.", _:stStatement); } else { DB::Debug("(stmt_free_result:%d) Nothing to free.", _:stStatement); } } stock stmt_close(&DBStatement:stStatement) { DB::LazyInitialize(); if (stStatement == DB::INVALID_STATEMENT || !(0 <= _:stStatement < sizeof(gs_Statements))) { DB::Noticef("(stmt_close) Invalid statement passed (%d).", _:stStatement); return; } if (gs_Statements[stStatement][e_dbrResult] != DB::INVALID_RESULT) db_free_result(gs_Statements[stStatement][e_dbrResult]); gs_Statements[stStatement][e_dbDatabase] = DB:0; DB::Debug("(stmt_close:%d) Closed statement.", _:stStatement); stStatement = DB::INVALID_STATEMENT; } stock stmt_autoclose(&DBStatement:stStatement) { DB::LazyInitialize(); if (stStatement == DB::INVALID_STATEMENT || !(0 <= _:stStatement < sizeof(gs_Statements))) { DB::Noticef("(stmt_autoclose) Invalid statement passed (%d).", _:stStatement); return; } if (gs_iAutoFreeTimer == -1) gs_iAutoFreeTimer = SetTimer("db_drain_autofree_pool", 0, false); if (gs_iAutoCloseStatementsIndex + 1 >= sizeof(gs_astAutoCloseStatements)) { DB::Warning("(stmt_autoclose) The autoclose pool is full!"); return; } gs_astAutoCloseStatements[gs_iAutoCloseStatementsIndex] = stStatement; gs_iAutoCloseStatementsIndex++; DB::Debug("(stmt_autoclose:%d) Will autoclose statement.", _:stStatement); } stock DBResult:db_query_hook(iTagOf3 = tagof(_bAutoRelease), DB:db, const szQuery[], {bool, DBDataType}:_bAutoRelease = true, {DBDataType, QQPA}:...) { new iIndex, iNumArgs, iStaticArgs = 4, bool:bAutoRelease = true ; #emit LOAD.S.pri 8 #emit SHR.C.pri 2 #emit STOR.S.pri iNumArgs DB::LazyInitialize(); if (iTagOf3 == tagof(DBDataType:)) { iStaticArgs = 3; } else { bAutoRelease = _bAutoRelease; } if (db_is_persistent(db)) { if (!db_is_valid_persistent(db)) { DB::Errorf("(db_query) Invalid persistent database given (%04x%04x).", _:db >>> 16, _:db & 0xFFFF); return DB::INVALID_RESULT; } iIndex = _:db & 0x7FFFFFFF; if (!gs_PersistentDatabases[iIndex][e_dbDatabase]) { if (!(gs_PersistentDatabases[iIndex][e_dbDatabase] = db_open(gs_PersistentDatabases[iIndex][e_szName]))) { DB::Errorf("(db_query) Failed to lazily open the database."); return DB::INVALID_RESULT; } if (gs_iClosePersistentTimer == -1) gs_iClosePersistentTimer = SetTimer("db_close_persistent", 0, false); } db = gs_PersistentDatabases[iIndex][e_dbDatabase]; } #if DB_DEBUG if (ispacked(szQuery)) { strunpack(gs_szBuffer, szQuery); DB::Debug("(db_query) Running query: %s", gs_szBuffer); } else { DB::Debug("(db_query) Running query: %s", szQuery); } #endif new DBResult:dbrResult ; if (iNumArgs > iStaticArgs) { if ((iNumArgs - iStaticArgs) & 0b1) { DB::Error("(db_query) Invalid argument count. Did you forget to use the correct prefix on the arguments (e.g. STRING:somestring)?"); return DB::INVALID_RESULT; } new DBStatement:stmt = db_prepare(db, szQuery); for (new i = iStaticArgs + 1; i < iNumArgs; i += 2) { // Load the address of argument #emit LCTRL 5 #emit LOAD.S.alt i #emit SHL.C.alt 2 #emit ADD #emit ADD.C 12 #emit MOVE.alt #emit LOAD.I #emit PUSH.pri #emit PUSH.alt if (i == 4) { // Load the address of argument #emit POP.pri #emit ADD.C 0xFFFFFFFC #emit LOAD.I #emit PUSH.pri } else { // Load the address of argument #emit POP.pri #emit ADD.C 0xFFFFFFFC #emit LOAD.I #emit LOAD.I #emit PUSH.pri } // Push the param index: (i - iStaticArgs) / 2 #emit LOAD.S.pri i #emit LOAD.S.alt iStaticArgs #emit SUB #emit SHR.C.pri 1 #emit PUSH.pri #emit PUSH.ADR stmt // Push the argument count #emit PUSH.C 16 // Push the return address #emit LCTRL 6 #emit ADD.C 28 #emit PUSH.pri // Call stmt_bind_value #emit CONST.pri stmt_bind_value #emit SCTRL 6 } dbrResult = db_query@(db, gs_Statements[stmt][e_szQuery]); stmt_close(stmt); } else { dbrResult = db_query@(db, szQuery); } if (dbrResult) { if (bAutoRelease) db_autofree_result(dbrResult); new iResultAddress = db_get_result_mem_handle(dbrResult) - DB::GetAmxBaseRelative(), iAddress, iRows, iCols, iDataAddress, iOffset ; iAddress = iResultAddress; #emit LREF.S.pri iAddress #emit STOR.S.pri iRows iAddress += 4; #emit LREF.S.pri iAddress #emit STOR.S.pri iCols iAddress += 4; #emit LREF.S.pri iAddress #emit STOR.S.pri iDataAddress iDataAddress -= DB::GetAmxBaseRelative(); iOffset = (iCols + iRows * iCols) * 4 - 4; while (iOffset >= 0) { iAddress = iDataAddress + iOffset; #emit LREF.S.pri iAddress #emit STOR.S.pri iAddress if (!iAddress) { new iAmxBaseRelative = DB::GetAmxBaseRelative() ; iAddress = iDataAddress + iOffset; #emit CONST.pri gs_szNull #emit LOAD.S.alt iAmxBaseRelative #emit ADD #emit SREF.S.pri iAddress } iOffset -= 4; } } return dbrResult; } stock db_get_field_hook(DBResult:dbresult, field, result[], maxlength = sizeof(result)) { new retval = db_get_field(dbresult, field, result, maxlength); format(result, maxlength, "%s", result); return retval; } stock db_get_field_assoc_hook(DBResult:dbresult, const field[], result[], maxlength = sizeof(result)) { new retval = db_get_field_assoc(dbresult, field, result, maxlength); format(result, maxlength, "%s", result); return retval; } stock bool:db_dump_table(DB:db, const szTable[], const szFilename[]) { static s_szColumnName[256] ; new DBResult:dbrResult, File:fp ; if (strfind(szTable, "\"") != -1 || strfind(szTable, "'") != -1) { DB::Error("(db_dump_table) Invalid table name given."); return false; } if (!(fp = fopen(szFilename, io_write))) { DB::Error("(db_dump_table) Failed to open the file."); return false; } format(gs_szBuffer, sizeof(gs_szBuffer), "SELECT sql FROM sqlite_master WHERE tbl_name = '%s'", szTable); dbrResult = db_query(db, gs_szBuffer); if (!dbrResult) { DB::Error("(db_dump_table) Failed to get the table sql."); return false; } db_get_field(dbrResult, 0, gs_szBuffer, sizeof(gs_szBuffer) - 1); if (strlen(gs_szBuffer) >= sizeof(gs_szBuffer) - 2) { DB::Error("(db_dump_table) Buffer overflow."); fwrite(fp, "\nBUFFER OVERFLOW"); fclose(fp); db_free_result(dbrResult); return false; } fwrite(fp, gs_szBuffer); fwrite(fp, ";\n\n\n"); db_free_result(dbrResult); format(gs_szBuffer, sizeof(gs_szBuffer), "PRAGMA table_info(%s)", szTable); dbrResult = db_query(db, gs_szBuffer, false); if (!dbrResult) { DB::Error("(db_dump_table) Failed to get table info."); return false; } gs_szBuffer = "SELECT 'INSERT INTO "; strcat(gs_szBuffer, szTable); strcat(gs_szBuffer, " VALUES('"); if (db_num_rows(dbrResult)) do { db_get_field(dbrResult, 1, s_szColumnName, sizeof(s_szColumnName) - 1); if (db_get_row_index(dbrResult) > 0) strcat(gs_szBuffer, " || ', '"); strcat(gs_szBuffer, " || quote("); strcat(gs_szBuffer, s_szColumnName); strcat(gs_szBuffer, ")"); } while (db_next_row(dbrResult)); strcat(gs_szBuffer, " || ');' FROM "); strcat(gs_szBuffer, szTable); db_free_result(dbrResult); dbrResult = db_query(db, gs_szBuffer); fwrite(fp, "BEGIN;\n\n"); if (db_num_rows(dbrResult)) do { db_get_field(dbrResult, 0, gs_szBuffer, sizeof(gs_szBuffer) - 1); if (strlen(gs_szBuffer) >= sizeof(gs_szBuffer) - 2) { DB::Error("(db_dump_table) Buffer overflow."); fwrite(fp, "\nBUFFER OVERFLOW"); fclose(fp); db_free_result(dbrResult); return false; } fwrite(fp, gs_szBuffer); fwrite(fp, "\n"); } while (db_next_row(dbrResult)); fwrite(fp, "\nCOMMIT;\n"); db_free_result(dbrResult); fclose(fp); return true; } stock DBResult:db_print_result(DBResult:dbrResult, iMaxFieldLength = 40) { DB::LazyInitialize(); const MAX_ROWS = 100, MAX_FIELDS = 20, MAX_FIELD_LENGTH = 88 ; static s_aaszFields[MAX_ROWS + 1][MAX_FIELDS][MAX_FIELD_LENGTH char], s_aiFieldMaxLength[MAX_FIELDS] ; static const szcSpacePadding[MAX_FIELD_LENGTH] = {' ', ...}, szcDashPadding[MAX_FIELD_LENGTH] = {'-', ...} ; if (iMaxFieldLength == -1) iMaxFieldLength = MAX_FIELD_LENGTH; print(!" "); print(!"Query result:"); if (!dbrResult) print(!"\t- Invalid result."); else if (!db_num_rows(dbrResult)) print(!"\t- No rows."); else { new iRow = 0, iRows, iFields = db_num_fields(dbrResult), iField, iLength, bool:bHasMoreLines, iPos, iNextPos, iRowIndex = db_get_row_index(dbrResult) ; db_set_row_index(dbrResult, 0); if (iMaxFieldLength > MAX_FIELD_LENGTH) { printf("\t- The longest possible field length is %d. Change MAX_FIELD_LENGTH for larger values.", MAX_FIELD_LENGTH); iMaxFieldLength = MAX_FIELD_LENGTH; } if (iFields > MAX_FIELDS) { printf("\t- There are %d, but only %d of them will be visible.", iFields, MAX_FIELDS); print(!"\t- Increase MAX_FIELDS if you want to see all fields."); iFields = MAX_FIELDS; } for (iField = 0; iField < iFields; iField++) { db_field_name(dbrResult, iField, gs_szBuffer, iMaxFieldLength - 1); iPos = 0; while (-1 != (iPos = strfind(gs_szBuffer, "\r", _, iPos))) gs_szBuffer[iPos] = ' '; iPos = 0; while (-1 != (iPos = strfind(gs_szBuffer, "\t", _, iPos))) { gs_szBuffer[iPos] = ' '; strins(gs_szBuffer, " ", iPos, iMaxFieldLength); iPos += 4; } iPos = 0; do { iNextPos = strfind(gs_szBuffer, "\n", _, iPos) + 1; if (!iNextPos) iLength = strlen(gs_szBuffer[iPos]); else iLength = iNextPos - iPos - 1; s_aiFieldMaxLength[iField] = min(iMaxFieldLength, max(iLength, s_aiFieldMaxLength[iField])); } while ((iPos = iNextPos)); strpack(s_aaszFields[0][iField], gs_szBuffer, iMaxFieldLength char); } do { for (iField = 0; iField < iFields; iField++) { if (db_field_is_null(dbrResult, iField)) gs_szBuffer = "NULL"; else db_get_field(dbrResult, iField, gs_szBuffer, iMaxFieldLength - 1); iPos = 0; while (-1 != (iPos = strfind(gs_szBuffer, "\r", _, iPos))) gs_szBuffer[iPos] = ' '; iPos = 0; while (-1 != (iPos = strfind(gs_szBuffer, "\t", _, iPos))) { gs_szBuffer[iPos] = ' '; strins(gs_szBuffer, " ", iPos, iMaxFieldLength); iPos += 4; } iPos = 0; do { iNextPos = strfind(gs_szBuffer, "\n", _, iPos) + 1; if (!iNextPos) iLength = strlen(gs_szBuffer[iPos]); else iLength = iNextPos - iPos - 1; s_aiFieldMaxLength[iField] = min(iMaxFieldLength, max(iLength, s_aiFieldMaxLength[iField])); } while ((iPos = iNextPos)); strpack(s_aaszFields[iRow + 1][iField], gs_szBuffer, iMaxFieldLength char); } if (++iRow >= MAX_ROWS) { iRows = iRow; while (db_next_row(dbrResult)) iRows++; printf("\t- Only the first %d rows are displayed; there are %d remaining.", MAX_ROWS, iRows); break; } } while (db_next_row(dbrResult)); print(!" "); for (iRows = iRow, iRow = 0; iRow <= iRows; iRow++) { do { bHasMoreLines = false; gs_szBuffer[0] = 0; for (iField = 0; iField < iFields; iField++) { if (iField) strcat(gs_szBuffer, " | "); iLength = strlen(gs_szBuffer); if (-1 != (iPos = strfind(s_aaszFields[iRow][iField], "\n"))) { strunpack(gs_szBuffer[iLength], s_aaszFields[iRow][iField], strlen(gs_szBuffer[iLength]) + iPos + 1); strdel(s_aaszFields[iRow][iField], 0, iPos + 1); bHasMoreLines = true; } else { if (s_aaszFields[iRow][iField]{0}) { strunpack(gs_szBuffer[iLength], s_aaszFields[iRow][iField], sizeof(gs_szBuffer) - iLength); s_aaszFields[iRow][iField]{0} = 0; } } iLength = strlen(gs_szBuffer[iLength]); strcat(gs_szBuffer, szcSpacePadding, strlen(gs_szBuffer) + (s_aiFieldMaxLength[iField] - iLength + 1)); } if (bHasMoreLines) printf("\t| %s |", gs_szBuffer); } while (bHasMoreLines); if (iRow == 0) { printf("\t/ %s \\", gs_szBuffer); } else { printf("\t| %s |", gs_szBuffer); } if (iRow == iRows) { gs_szBuffer[0] = 0; for (iField = 0; iField < iFields; iField++) { if (iField) strcat(gs_szBuffer, "---"); strcat(gs_szBuffer, szcDashPadding, strlen(gs_szBuffer) + s_aiFieldMaxLength[iField] + 1); } printf("\t\\-%s-/", gs_szBuffer); } else { gs_szBuffer[0] = 0; for (iField = 0; iField < iFields; iField++) { if (iField) strcat(gs_szBuffer, "-|-"); strcat(gs_szBuffer, szcDashPadding, strlen(gs_szBuffer) + s_aiFieldMaxLength[iField] + 1); } printf("\t|-%s-|", gs_szBuffer); } } db_set_row_index(dbrResult, iRowIndex); } print(!" "); return dbrResult; } stock db_print_query(DB:db, const szQuery[], iMaxFieldLength = 40) { new DBResult:dbrResult = db_query(db, szQuery, false) ; db_print_result(dbrResult, iMaxFieldLength); db_free_result(dbrResult); } forward db_drain_autofree_pool(); public db_drain_autofree_pool() { DB::LazyInitialize(); gs_iAutoFreeTimer = -1; for (new i = gs_iAutoFreeResultsIndex; i--; ) { if (gs_adbrAutoFreeResults[i]) { new DBResult:result = gs_adbrAutoFreeResults[i]; gs_adbrAutoFreeResults[i] = DB::INVALID_RESULT; DB::Debug("(db_drain_autofree_pool) Autofreeing 0x%04x%04x", _:result >>> 16, _:result & 0xFFFF); db_free_result_hook(result); } } gs_iAutoFreeResultsIndex = 0; for (new i = gs_iAutoCloseStatementsIndex; i--; ) { if (gs_astAutoCloseStatements[i]) { DB::Debug("(db_drain_autofree_pool) Autoclosing statement %d.", _:gs_astAutoCloseStatements[i]); stmt_close(gs_astAutoCloseStatements[i]); } } gs_iAutoCloseStatementsIndex = 0; } forward db_free_stmt_results(); public db_free_stmt_results() { gs_iFreeStatementResultsTimer = -1; for (new DBStatement:i = DBStatement:0; _:i < sizeof(gs_Statements); i++) { if (gs_Statements[i][e_dbDatabase] && gs_Statements[i][e_bAutoFreeResult] && gs_Statements[i][e_dbrResult] != DB::INVALID_RESULT) { new DBResult:result = gs_Statements[i][e_dbrResult]; gs_Statements[i][e_dbrResult] = DB::INVALID_RESULT; DB::Debug("(db_free_stmt_results) Freeing 0x%04x%04x for %d.", _:result >>> 16, _:result & 0xFFFF, _:i); db_free_result_hook(result); } } } forward db_close_persistent(); public db_close_persistent() { gs_iClosePersistentTimer = -1; for (new i = 0; i < sizeof(gs_PersistentDatabases); i++) { if (gs_PersistentDatabases[i][e_bIsUsed] && gs_PersistentDatabases[i][e_dbDatabase]) { db_close@(gs_PersistentDatabases[i][e_dbDatabase]); gs_PersistentDatabases[i][e_dbDatabase] = DB:0; } } } static stock DB::FindMSB(iInput) { // http://graphics.stanford.edu/~seander/bithacks.html#IntegerLogDeBruijn static const s_aiDeBruijnBitPositionsPacked[32 char] = { 0x0A010900, 0x1D02150D, 0x12100E0B, 0x1E031916, 0x1C140C08, 0x0718110F, 0x06171B13, 0x1F04051A } ; if (iInput) { #emit LOAD.S.pri iInput #emit MOVE.alt #emit SHR.C.alt 1 #emit OR #emit MOVE.alt #emit SHR.C.alt 2 #emit OR #emit MOVE.alt #emit SHR.C.alt 4 #emit OR #emit MOVE.alt #emit SHR.C.alt 8 #emit OR #emit MOVE.alt #emit SHR.C.alt 16 #emit OR #emit CONST.alt 0x07C4ACDD #emit UMUL #emit SHR.C.pri 27 #emit ADD.C s_aiDeBruijnBitPositionsPacked #emit LODB.I 1 #emit RETN } return -1; } static stock DB::getstringarg(dest[], arg, len = sizeof (dest)) { // Get the address of the previous function's stack. First get the index of // the argument required. #emit LOAD.S.pri arg // Then convert that number to bytes from cells. #emit SMUL.C 4 // Get the previous function's frame. Stored in variable 0 (in the current // frame). Parameters are FRM+n+12, locals are FRM-n, previous frame is // FRM+0, return address is FRM+4, parameter count is FRM+8. We could add // checks that "arg * 4 < *(*(FRM + 0) + 8)", for the previous frame parameter // count (in C pointer speak). #emit LOAD.S.alt 0 // Add the frame pointer to the argument offset in bytes. #emit ADD // Add 12 to skip over the function header. #emit ADD.C 12 // Load the address stored in the specified address. #emit LOAD.I // Push the length for "strcat". #emit PUSH.S len // Push the address we just determined was the source. #emit PUSH.pri // Load the address of the destination. #emit LOAD.S.alt dest // Blank the first cell so "strcat" behaves like "strcpy". #emit CONST.pri 0 // Store the loaded number 0 to the loaded address. #emit STOR.I // Push the loaded address. #emit PUSH.alt // Push the number of parameters passed (in bytes) to the function. #emit PUSH.C 12 // Call the function. #emit SYSREQ.C strcat // Restore the stack to its level before we called this native. #emit STACK 16 } static stock DB::setstringarg(iArg, const szValue[], iLength = sizeof(szValue)) { new iAddress ; // Get the address of the previous function's stack. First get the index of // the argument required. #emit LOAD.S.pri iArg // Then convert that number to bytes from cells. #emit SMUL.C 4 // Get the previous function's frame. #emit LOAD.S.alt 0 // Add the frame pointer to the argument offset in bytes. #emit ADD // Add 12 to skip over the function header. #emit ADD.C 12 // Load the address stored in the specified address. #emit LOAD.I #emit STOR.S.PRI iAddress // Push the length (last argument first) #emit PUSH.S iLength // Push the new value (source) szValue #emit PUSH.S szValue // Blank out the first cell of the argument #emit CONST.pri 0 #emit SREF.S.pri iAddress // Push the destination #emit PUSH.S iAddress // Push the number of parameters passed (in bytes) to the function. #emit PUSH.C 12 // Call the function. #emit SYSREQ.C strcat // Restore the stack to its level before we called this native. #emit STACK 16 } // Pretty much Y_Less's va_strlen function static stock DB::isargpacked(iArg) { // Get the length of the string at the given position on the previous // function's stack (convenience function). // Get the address of the previous function's stack. First get the index of // the argument required. #emit LOAD.S.pri iArg // Then convert that number to bytes from cells. #emit SMUL.C 4 // Get the previous function's frame. Stored in variable 0 (in the current // frame). Parameters are FRM+n+12, locals are FRM-n, previous frame is // FRM+0, return address is FRM+4, parameter count is FRM+8. We could add // checks that "arg * 4 < *(*(FRM + 0) + 8)", for the previous frame parameter // count (in C pointer speak). #emit LOAD.S.alt 0 // Add the frame pointer to the argument offset in bytes. #emit ADD // Add 12 to skip over the function header. #emit ADD.C 12 // Load the address stored in the specified address. #emit LOAD.I // Push the address we just determined was the source. #emit PUSH.pri // Push the number of parameters passed (in bytes) to the function. #emit PUSH.C 4 // Call the function. #emit SYSREQ.C ispacked // Restore the stack to its level before we called this native. #emit STACK 8 #emit RETN // Never called. return 0; } stock DB::GetAmxBaseRelative() { static s_iAmxBaseRelative = 0 ; if (!s_iAmxBaseRelative) { s_iAmxBaseRelative = DB::GetAmxBase(); #emit LCTRL 1 #emit LOAD.alt s_iAmxBaseRelative #emit ADD #emit STOR.pri s_iAmxBaseRelative } return s_iAmxBaseRelative; } // By Zeex! // Returns the AMX base address i.e. amx->base. static stock DB::dummy() { return 0; } stock DB::GetAmxBase() { static amx_base = 0; // cached if (amx_base == 0) { new cod, dat; #emit lctrl 0 #emit stor.s.pri cod #emit lctrl 1 #emit stor.s.pri dat // Get code section start address relative to data. new code_start = cod - dat; // Get address of DB::dummy(). new fn_addr; #emit const.pri DB_dummy #emit stor.s.pri fn_addr // Get absolute address from the CALL instruction. new fn_addr_reloc, call_addr; DB_dummy(); #emit lctrl 6 #emit stor.s.pri call_addr call_addr = call_addr - 12 + code_start; #emit lref.s.pri call_addr #emit stor.s.pri fn_addr_reloc amx_base = fn_addr_reloc - fn_addr - cod; } return amx_base; } // phys_memory.inc static stock AbsToRel(addr) { new dat; #emit lctrl 1 #emit stor.s.pri dat return addr - (GetAmxBaseAddress() + dat); } // This function has a bug in older amx_assembly versions static stock WritePhysMemoryCell_(addr, what) { new rel_addr = AbsToRel(addr); #emit load.s.pri what #emit sref.s.pri rel_addr #emit stack 4 #emit retn return 0; // make compiler happy } // Hook db_get_field // This is done lastly because the fixed function isn't needed within SQLitei #define db_get_field db_get_field_hook #define db_get_field_assoc db_get_field_assoc_hook