[SourcePawn] Урок 13 - Работа с базами данных (MySQL, SQLite)

R1KO

fuck society
Команда форума
Сообщения
8,882
Реакции
6,539
[SourcePawn] Урок 13 - Работа с базами данных (MySQL, SQLite)

<- К содержанию

Подробнее о SQL запросах

Основные типы при работе с базами данных
  • Database - производный тип от Handle, который являет собой соединение с базой.
    • Методы Database
      • Connect - Подключается к базе данных.
        PHP:
        void Connect(SQLConnectCallback callback, const char[] name="default", any data=0);
        • SQLConnectCallback callback - Обратный вызов.
        • char[] name - Имя секции для подключения в файле databases.cfg.
        • any data - Данные для передачи в обратный вызов. По умолчанию = 0.
      • *Примечание: Метод Connect имеет тип static. Следовательно вызываться должен не от экземпляра типа, а от имени типа.
        PHP:
        Database g_hDatabase; // Глобальная переменная для соединения с базой
        
        public void OnPluginStart()
        {
        Database.Connect(ConnectCallBack, "sp_lessons"); // sp_lessons Имя секции в databases.cfg
        }
        
        public void ConnectCallBack (Database hDB, const char[] szError, any data) // Пришел результат соединения
        {
        if (hDB == null || szError[0]) // Соединение не удачное
        {
        SetFailState("Database failure: %s", szError); // Отключаем плагин
        return;
        }
        
        g_hDatabase = hDB; // Присваиваем глобальной переменной соединения значение текущего соединения
        CreateTables(); // Функция пока не реализована, но по имени, думаю, ясно что она делает
        }
      • SetCharset - Устанавливает набор символов при работе с базой.
        PHP:
        bool SetCharset(const char[] charset)
        • char[] charset - Набор символов (Например: "utf8" или "latin1")
      • PHP:
        g_hDatabase.SetCharset("utf8");
      • Query - Выполняет запрос к базе.
        PHP:
        void Query(SQLQueryCallback callback, const char[] query, any data, DBPriority prio)
        • SQLQueryCallback callback - Обратный вызов.
        • char[] query - Запрос
        • any data - Данные для передачи в обратный вызов. По умолчанию = 0.
        • DBPriority prio - Приоритет выполнения. По умолчанию = DBPrio_Normal.
      • PHP:
        public void OnClientPostAdminCheck(int iClient)
        {
        if(IsFakeClient(iClient) == false)
        {
        char szQuery[256], szAuth[32];
        GetClientAuthId(iClient, AuthId_Engine, szAuth, sizeof(szAuth), true);
        FormatEx(szQuery, sizeof(szQuery), "SELECT `id`, `points` FROM `table_players` WHERE `auth` = '%s';", szAuth); // Формируем запрос
        g_hDatabase.Query(SQL_Callback_SelectClient, szQuery, GetClientUserId(iClient));
        }
        }
        
        public void SQL_Callback_SelectClient(Database hDatabase, DBResultSet results, const char[] sError, any iUserID) // Обратный вызов
        {
        if(sError[0]) // Если произошла ошибка
        {
        LogError("SQL_Callback_SelectClient: %s", sError); // Выводим в лог
        return; // Прекращаем выполнение ф-и
        }
        
        int iClient = GetClientOfUserId(iUserID);
        if(iClient)
        {
        // Игрок всё еще на сервере
        }
        }
      • Execute - Отправляет транзакцию.
        PHP:
        void Execute(Transaction txn, SQLTxnSuccess onSuccess, SQLTxnFailure onError, any data, DBPriority priority)
        • Transaction txn - Экземпляр типа Transaction.
        • SQLTxnSuccess onSuccess - Обратный вызов в случае успеха.
        • SQLTxnFailure onError - Обратный вызов в случае неудачи.
        • any data - Данные для передачи в обратный вызов. По умолчанию = 0.
        • DBPriority priority - Приоритет выполнения. По умолчанию = DBPrio_Normal.
      • PHP:
        void MyFunc()
        {
        Transaction hTxn = new Transaction();
        hTxn.AddQuery("запрос 1;", 8); // Как данные передадим число 8
        hTxn.AddQuery("запрос 2;", 20); // Как данные передадим число 20
        // ...
        hTxn.AddQuery("запрос 52;");
        
        g_hDatabase.Execute(hTxn, SQL_TxnCallback_Success, SQL_TxnCallback_Failure, 150); // Как данные передадим число 150
        }
        
        public void SQL_TxnCallback_Success(Database hDatabase, any Data, int iNumQueries, DBResultSet[] results, any[] QueryData)
        {
        // SQL_TxnCallback_Success
        // Data = 150
        // iNumQueries = кол-во запросов
        // DBResultSet[] results = массив результатов запросов
        // any[] QueryData = массив данных запросов
        
        
        }
        
        public void SQL_TxnCallback_Failure(Database hDatabase, any Data, int iNumQueries, const char[] szError, int iFailIndex, any[] QueryData)
        {
        // Один или больше запросов прошли неудачно
        LogError("SQL_TxnCallback_Failure: %s", szError);
        }
      • Escape - Экранирует символы ' в строке.'
        PHP:
        bool Escape(const char[] string, char[] buffer, int maxlength, int &written)
        • char[] string - Исходная строка
        • char[] buffer - Буфер, куда будет помещен результат (Размер должен быть 2*strlen(string)+1)
        • int maxlength - Размер буфера
        • int &written - Буфер, в который будет записано количество записанных байт. По умолчанию = 0.
      • PHP:
        char szName[MAX_NAME_LENGTH];
        GetClientName(iClient, szName, sizeof(szName));
        int iLen = 2*strlen(szName)+1;
        char[] szEscapedName = new char[iLen ];
        g_hDatabase.Escape(szName, szEscapedName, iLen );
      • IsSameConnection - Проверяет совпадают ли соединения.
        PHP:
        bool IsSameConnection(Database other)
        • Database other - Соединение с базой данных
      • PHP:
        Database g_hDatabase2 // Другое соединение
        if(g_hDatabase.IsSameConnection(g_hDatabase2))
        {
        PrintToServer("Подключения ведут к одной базе данных!");
        }
        else
        {
        PrintToServer("Это разные соединения");
        }
    • Свойства Database
      • Driver - Возвращает драйвер подключения к базе данных (тип DBDriver).
  • DBDriver- драйвер базы данных.
    • Методы DBDriver
      • Find - Находит драйвер по имени.
        PHP:
        DBDriver Find(const char[] name);
        • char[] name - Строка идентификации драйвера ("mysql" или "sqlite")
      • GetIdentifier - Получает имя драйвера.
        PHP:
        void GetIdentifier(char[] ident, int maxlength)
        • char[] ident - Буфер, куда будет помещена строка идентификации драйвера ("mysql" или "sqlite")
        • int maxlength - Размер буфера
      • GetProduct - Получает продукт драйвера.
        PHP:
        void GetProduct(char[] product, int maxlength)
        • char[] product - Буфер, куда будет помещен продукт ("MySQL" или "SQLite")
        • int maxlength - Размер буфера
  • DBStatement- представляет собой предварительно скомпилированный запрос SQL, который может выполняться несколько раз с различными параметрами.
    • Методы DBStatement
      • BindInt - Назначает параметр в подготовленном запросе для целого значения.
        PHP:
        void BindInt(int param, int number, bool signed)
        • int param - Индекс параметра (начинается с 0)
        • int number - Число для назначения
        • bool signed - true - знаковое число, false - беззнаковое
      • BindFloat - Назначает параметр в подготовленном запросе для числа с плавающей точкой.
        PHP:
        void BindFloat(int param, float value)
        • int param - Индекс параметра (начинается с 0)
        • float value - Число для назначения
      • BindString - Назначает параметр в подготовленном запросе для строки.
        PHP:
        void BindString(int param, const char[] value, bool copy)
        • int param - Индекс параметра (начинается с 0)
        • char[] value - Строка для назначения
        • bool copy - Если true - будет использована копия переменной (если значение переменной изменится то в запросе оно останется тем же).
  • Transaction- транзакция (набор запросов, которые связанны между собой и либо выполняются все, либо ни один из них).
    • Методы Transaction
      • Transaction - Создает объект типа Transaction.
        PHP:
        Transaction hTxn = new Transaction();
      • AddQuery - Добавляет запрос в транзакцию. Возвращает индекс запроса в списке транзакции.
        PHP:
        int AddQuery(const char[] query, any data)
        • char[] query - Запрос.
        • any data - Данные для передачи в обратный вызов. По умолчанию = 0.
      • PHP:
        Transaction hTxn = new Transaction();
        hTxn.AddQuery("UPDATE `table_players` SET `points` = 0;");
        hTxn.AddQuery("UPDATE `table_players` SET `points` = 100 WHERE `id` = 5;", 5); // Как данные передадим цифру 5
  • DBResultSet- набор результатов, возвращаемых из запроса.
    • Методы DBResultSet
      • FetchRow - Возвращает строку из текущего набора результатов. Это нужно выполнять перед извлечением результатов. Возвращает true в случае успеха.
        PHP:
        if(hResult.FetchRow()) // Если условие выполнено - можно получать данные
        {
        // Здесь будем получать данные
        }
        // Если запрос должен вернуть не 1, а несколько строк то можно сделать так:
        while(hResult.FetchRow()) // Тело цикла будет выполнятся пока можно получать данные
        {
        // Здесь будем получать данные
        }
      • FetchInt - Возвращает целочисленное значение из текущей строки. Если NULL - вернет 0.
        PHP:
        int FetchInt(int field, DBResult &result)
        • int field - Индекс поля (начинается с 0).
        • DBResult &result - Буфер, для сохранения статуса, возвращаемого значения. По умолчанию = 0.
      • PHP:
        // Помните запрос?
        // "SELECT `id`, `points` FROM `table_players` WHERE `auth` = '%s';"
        // Нумерация полей начинается с 0, следовательно:
        // id - 0
        // points - 1
        
        // Получаем значение поля id
        int ID = hResult.FetchInt(0);
        // Получаем значение поля points
        int iPoints = hResult.FetchInt(1);
      • FetchFloat - Возвращает значение с плавающей точкой из текущей строки. Если NULL - вернет 0.0.
        PHP:
        float FetchFloat(int field, DBResult &result)
        • int field - Индекс поля (начинается с 0).
        • DBResult &result - Буфер, для сохранения статуса, возвращаемого значения. По умолчанию = 0.
      • PHP:
        // "SELECT (CAST(kills as float)/CAST(daths as float)) as kdr FROM `table_players` WHERE `id` = 6;"
        // Получим соотношение кол-ва убийств к кол-ву смертей
        
        float fKDR = hResult.FetchFloat(0);
      • FetchString - Возвращает строковое значение из текущей строки. Если NULL - вернет "".
        PHP:
        int FetchString(int field, char[] buffer, int maxlength, DBResult &result)
        • int field - Индекс поля (начинается с 0).
        • char[] buffer - Буфер, для записи строки.
        • int maxlength - Размер буфера.
        • DBResult &result - Буфер, для сохранения статуса, возвращаемого значения. По умолчанию = 0.
      • PHP:
        // "SELECT `clan_tag` FROM `table_players` WHERE `id` = 8;"
        // Получим соотношение кол-ва убийств к кол-ву смертей
        
        char szClanTag[32];
        hResult.FetchString(0, szClanTag, sizeof(szClanTag));
      • FetchSize - Возвращает размер строки в текущей строке набора. Это нужно вызывать только для строк, чтобы определить какого размера необходимо создать буфер. Обратите внимание, что возвращаемое значение не включает в себя нулевой символ.
        PHP:
        int FetchSize(int field)
        • int field - Индекс поля (начинается с 0).
      • PHP:
        // С использованием этого метода, предыдущий код можно написать так:
        int iSize = hResult.FetchSize(0)+1;
        char[] szClanTag = new char[iSize];
        hResult.FetchString(0, szClanTag, iSize );
      • IsFieldNull - Возвращает является ли поле NULL.
        PHP:
        bool IsFieldNull(int field)
        • int field - Индекс поля (начинается с 0).
      • PHP:
        if(hResult.IsFieldNull(0))
        {
        // поле `clan_tag` является NULL
        }
      • FieldNumToName - Получает имя поля по его индексу.
        PHP:
        void FieldNumToName(int field, char[] name, int maxlength)
        • int field - Индекс поля (начинается с 0).
        • char[] name - Буфер, для записи имени поля.
        • int maxlength - Размер буфера.
      • PHP:
        char szFieldName[32];
        hResult.FieldNumToName(0, szFieldName, sizeof(szFieldName));
        // szFieldName = "clan_tag"
      • FieldNameToNum - Получает индекс поля по его имени.
        PHP:
        bool FieldNameToNum(const char[] name, int &field)
        • char[] name - Имя поля.
        • int &field - Буфер, для записи индекса пол).
      • PHP:
        int iField;
        if(FieldNameToNum("clan_tag", iField))
        {
        // iField = 0
        }
      • FetchMoreResults - Переход к следующему набору результатов.
        В некоторых реализациях SQL, несколько наборов результатов могут существовать на одном запросе. Это возможно в MySQL с помощью простых запросов при выполнении запроса CALL. Если дело обстоит именно так, то все наборы результатов должны быть обработаны до того как другой запрос был сделан.
        PHP:
        bool FetchMoreResults()
    • Свойства DBResultSet
      • HasResults - Возвращает, существует ли или нет набор результатов. Это возвращает true, даже если были возвращены 0 результаты, но false на запросах как UPDATE, INSERT или DELETE.
      • RowCount - Возвращает количество строк в последнем наборе результатов.
      • FieldCount - Возвращает количество полей в последнем наборе результатов.
      • MoreRows - Возвращает, есть ли еще строки.
      • AffectedRows - Возвращает, измененных/удаленных (затронутых) запросом строк.
      • InsertId - Возвращает, последний id из INSERT запроса.
  • Transaction- представляет собой набор запросов SQL, который являются зависимыми друг от друга. Либо все должны быть выполнены успешно, либо не будет выполнен ни один.
    • Методы Transaction
      • Transaction - Создает новую транзакцию.
        PHP:
        Transaction hTxn = new Transaction();
      • AddQuery - Добавляет SQL запрос в транзакцию.
        PHP:
        int AddQuery(const char[] query, any data)
        • char query - SQL запрос
        • any data - Данные для передачи
      • PHP:
        hTxn.AddQuery("запрос 1;", 8); // Как данные передадим число 8


В одном и том же соединении можно запускать как поточные, так и не-поточные запросы.
Однако без надлежащих мер предосторожности вы можете повредить сетевой поток (даже если он локальный), поврежденную память или иным образом вызвать сбой в драйвере SQL.
Чтобы решить эту проблему, SourceMod имеет блокировку базы данных.
Блокировка выполняется через SQL_LockDatabase и SQL_UnlockDatabase.

Всякий раз, когда выполняется какая-либо из следующих операций, не связанных с потоком в базе данных, абсолютно необходимо заключить всю операцию с блокировкой:

  • Сопряжения SQL_QuerySQL_FetchMoreResults)
  • SQL_FastQuery
  • SQL_PrepareQuery
  • Сопряжения SQL_Bind и SQL_Execute
Эмпирическое правило: если ваша операция будет использоваться для подключения к базе данных, она должна быть заблокирована до завершения операции.

Пример:
PHP:
bool GetByAge_Query(Database hDdb, int iAge)
{
    char szQuery[128];
    FormatEx(szQuery, sizeof(szQuery), "SELECT `name` FROM `users` WHERE `age` = %d;", iAge)

    // Блокируем все запросы в базу данных
    SQL_LockDatabase(hDdb);
    // Выполняем не-поточный запрос
    DBResultSet hQuery = SQL_Query(hDdb, szQuery);
 
    // Разблокируем базу.
    SQL_UnlockDatabase(hDdb);
 
    if (hQuery == INVALID_HANDLE)
    {
        return false;
    }

    // Выводим результаты запроса

    delete hQuery;

    return true;
}
Обратите внимание, что нужно только заблокировать запрос;
SourceMod предварительно выбирает набор результатов, и, следовательно, сетевая очередь чиста.

Предупреждение:
Никогда не вызывайте SQL_LockDatabase прямо перед поточной операцией.
Вы заблокируете сервер и должны его прекратить / убить.
Всегда разблокируйте базу после блокировки.
Если ваш запрос возвращает несколько наборов результатов, например, вызов процедуры в MySQL, который возвращает результаты, вы должны заблокировать как запрос, так и всю операцию выборки.
SourceMod может только получить один результирующий набор за раз, и все результирующие наборы должны быть очищены до запуска нового запроса.

В качестве примера простой плагин статистики (приложил файлом)

Подкиньте идей о чем можно примеров написать и о каких ф-ях рассказать
 

Вложения

Последнее редактирование:

Right is Left

Социопат
Команда форума
Сообщения
9,275
Реакции
7,572
В целом, все хорошо, как всегда на высоте. Но заметил такую нестыковочку.

Если ф-ия FetchSize() возвращает размер строки без нулевого байта:
Обратите внимание, что возвращаемое значение не включает в себя нулевой символ.
То почему в примере не добавляется один байт?)
PHP:
// С использованием этого метода, предыдущий код можно написать так:
char[] szClanTag = new char[hResult.FetchSize(0)];
hResult.FetchString(0, szClanTag, sizeof(szClanTag));
И разве sizeof() работает на динамических массивах? Я где-то видел, что это конструкция языка, и при компиляции, она заменяется на размер массива.
Эту теорию так же можно проверить, закомпилировав любой плагин с вызовом sizeof(), а потом произведя декомпиляцию.
 
  • Мне нравится
Реакции: R1KO

Right is Left

Социопат
Команда форума
Сообщения
9,275
Реакции
7,572
Ещё заметил случайно:
PHP:
// Помните запрос?
// "SELECT `id`, `points` FROM `table_players` WHERE `auth` = '%s';"
// Нумерация полей начинается с 0, следовательно:
// id - 0
// points - 0
Обе колонки нулем представлены, когда это не так. Торопился опубликовать?)
 

R1KO

fuck society
Команда форума
Сообщения
8,882
Реакции
6,539
@Kruzya, писал давно и по ночам.
 

Jafa

Участник
Сообщения
159
Реакции
3
@R1KO, Здравствуйте, подскажите пожалуйста как можно получить все данные с таблицы для записи их в массив?
В таблице N строк и 5 столбцов VARCHAR (1 PRIMARY).

char array[1000][5][125]; - наш массив в который нужно перегнать все что есть в базе. (N полюбому будет меньше 1000)

Надеюсь на вашу помочь и хороший пример с пояснением =) Спасибо.
--- Добавлено позже ---
@R1KO, если не затруднит дайте еще пример с динамическим массивом, думаю так будет логичней чтоб не создавать массив в 1000 размером когда он не будет заполнен т.к N будет меньше всегда.
 

R1KO

fuck society
Команда форума
Сообщения
8,882
Реакции
6,539
@Jafa,
PHP:
// отправляем запрос
g_hDatabase.Query(SQL_Callback_SelectRows, "SELECT * FROM `table`;");


// Пришел ответ на запрос
public void SQL_Callback_SelectRows(Database hDatabase, DBResultSet hResults, const char[] sError, any data)
{
    if(sError[0]) // Если произошла ошибка
    {
        LogError("SQL_Callback_SelectRows: %s", sError); // Выводим в лог
        return; // Прекращаем выполнение ф-и
    }

    int iRows = hResults.RowCount; // Количество полученных записей

    if(iRows)
    {
        int iFields = hResults.FieldCount; // Количество столбцов
        char[][][] sBuffer = new char[iRows][iFields][256];
       
        int i = 0, j; // индексы
        while(hResults.FetchRow())
        {
            for(j = 0; j < iFields; ++j)
            {
                hResults.FetchString(j, sBuffer[i][j], 256);
            }
            ++i;
        }
    }
}
 

Right is Left

Социопат
Команда форума
Сообщения
9,275
Реакции
7,572
Только сейчас заметил что sourcepawn может создавать char pointer? Динамическая строка?
Может, но sizeof() на таком не срабатывает, т.к. это конструкция языка. Потому размер строки надо где-то хранить.
 

FrozDark

Команда сайта HLMod
Сообщения
1,789
Реакции
2,041
Может, но sizeof() на таком не срабатывает, т.к. это конструкция языка. Потому размер строки надо где-то хранить.
я это и не спрашиваю если что. sizeof() при компиляции меняется на размер массива, т.е. вместо неё ставит цифру
 

R1KO

fuck society
Команда форума
Сообщения
8,882
Реакции
6,539
Только сейчас заметил что sourcepawn может создавать char pointer? Динамическая строка?
да
Introduction to SourcePawn 1.7 - AlliedModders Wiki

can be dynamically sized, by putting the brackets before the name. For example,

PHP:
int[] numbers = new int[MaxClients]
This creates an array of size MaxClients, which could be anything, so the size of the array is not known until the array is allocated.
 

Rostu

Участник
Сообщения
980
Реакции
553
@R1KO, Глупый вопрос который мне не понятен. Кусок твоего примера
PHP:
// Игрок получил урон
public void Event_PlayerHurt(Event hEvent, const char[] sEvName, bool bDontBroadcast)
{
    int iAttacker = GetClientOfUserId(hEvent.GetInt("attacker")); // Получаем атакующего
    if(iAttacker && !IsFakeClient(iAttacker))
    {
        if(hEvent.GetInt("hitgroup") == 1) // Если попадание в голову
        {
            ++g_iHits_hs[iAttacker];
        }
        else
        {
            ++g_iHits[iAttacker];
        }
    }
}

// Игрок умер
public void Event_PlayerDeath(Event hEvent, const char[] sEvName, bool bDontBroadcast)
{
    int iClient = GetClientOfUserId(hEvent.GetInt("userid")); // Получаем жертву
    if(iClient)
    {
        ++g_iDeaths[iClient]; // Прибавляем смерть
        
        int iAttacker = GetClientOfUserId(hEvent.GetInt("attacker"));    // Получаем убийцу
        if(iAttacker && !IsFakeClient(iAttacker))
        {
            if(hEvent.GetBool("headshot"))    // Если в голову
            {
                ++g_iKills_hs[iAttacker];
            }
            else
            {
                ++g_iKills[iAttacker];
            }
        }
    }
}
1)Почему не нужно записывать/обновлять сразу? 2) Когда произойдет обновление в бд ?
 

Right is Left

Социопат
Команда форума
Сообщения
9,275
Реакции
7,572
1)Почему не нужно записывать/обновлять сразу?
Оптимизационный момент. Лучше пару раз помучать БД (при коннекте, и дисконнекте игрока, соответственно), чем постоянно её насиловать.

2) Когда произойдет обновление в бд ?
Как только игрок выйдет с сервера.
 

Jafa

Участник
Сообщения
159
Реакции
3
Если мы в базу записываем ники игроков а они моут быть разнообразные и это может вызвать ошибку в построении запроса... можно как-то ник игрока перед добавлением в запрос "обезопасить" для самого запроса?

GlobalCallback: Could not update|insert in database, Reason: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'а') ON DUPLICATE KEY UPDATE name='Ф' at line 1 - ВОТ ЧТО В ЛОГАХ
Запрос выглядел так: INSERT INTO id (sid,name) VALUES ('%s','%s') ON DUPLICATE KEY UPDATE name='%s'

Ошибка будет если в нике у игрока будет символ ' как можно предотвратить ошибку?
--- Добавлено позже ---
@R1KO, Уделите секундочку пожалуйста)
--- Добавлено позже ---
SQL_EscapeString вот один вариант.. мбм еще есть какие-нибудь меры для больше безопастности?
--- Добавлено позже ---
@R1KO,
char szQuery[256], szName[MAX_NAME_LENGTH*2+1];
GetClientName(iClient, szQuery, MAX_NAME_LENGTH);
g_hDatabase.Escape(szQuery, szName, sizeof(szName));
поясните вот эту строку.. почему нельзя сделать просто:
char szQuery[MAX_NAME_LENGTH];
GetClientName(iClient, szQuery, MAX_NAME_LENGTH);
g_hDatabase.Escape(szQuery, szQuery, MAX_NAME_LENGTH);

т.е просто чтоб сама переменная та же изменилась под требования Escape?
--- Добавлено позже ---
А все нашел)) Буфер должен быть по крайней мере 2 * STRLEN (строка) +1.

Сам ответил на свой вопрос)
 
Последнее редактирование:

Drop

Участник
Сообщения
41
Реакции
1
Как сделать кик клиента, в случае если запрос в БД не сработал?
 

R1KO

fuck society
Команда форума
Сообщения
8,882
Реакции
6,539
@Drop, ну а наброски кода то есть?
 

Drop

Участник
Сообщения
41
Реакции
1
@R1KO, да конечно. забыл выложить. в данный момент вот так, но при таком коде, плагин кикает вне зависимости от того, есть запись в БД или её нет.

public OnClientPostAdminCheck(client)
{
if (!IsFakeClient(client))
{
decl String:steamid[32],String:clientname[24];
decl String:country[45];
decl String:ip[64];

GetClientName(client, clientname, sizeof(clientname));
GetClientIP(client, ip, sizeof(ip));
GeoipCountry(ip, country, sizeof(country));
GetClientAuthString(client,steamid,sizeof(steamid));
char szQuery[256], szAuth[32];

GetClientAuthId(client, AuthId_Engine, szAuth, sizeof(szAuth), true);
FormatEx(szQuery, sizeof(szQuery), "UPDATE IGNORE `users` SET `steamid` = '%s' WHERE `ip` = '%s';", steamid, ip); // Формируем запрос
g_hDatabase.Query(SQL_Callback_SelectClient, szQuery, GetClientUserId(client));


}


}
public void SQL_Callback_SelectClient(Database hDatabase, DBResultSet results, const char[] sError, any iUserID) // Обратный вызов
{
if(sError[0]) // Если произошла ошибка
{

LogError("SQL_Callback_SelectClient: %s", sError); // Выводим в лог
return; // Прекращаем выполнение ф-и
}
int Client = GetClientOfUserId(iUserID);
if (Client > 0 && SQL_GetAffectedRows(results) < 1 && !IsClientInKickQueue(Client))
{

KickClient(Client);
}
 

R1KO

fuck society
Команда форума
Сообщения
8,882
Реакции
6,539
@Drop, на чистой базе так же?
 

Drop

Участник
Сообщения
41
Реакции
1
@Drop, на чистой базе так же?
Что вы подразумеваете?
Если вы говорите о том, так же ли происходит, когда в БД совсем нет записей - тогда да. Если вы говорите о том, что в БД нет структуры - тогда не проверял.
 

R1KO

fuck society
Команда форума
Сообщения
8,882
Реакции
6,539
@Drop, проверяй что возвращает SQL_GetAffectedRows, вероятно в ней проблема. Как вариант: делать выборку, а при наличии записи - обновлять. Т.е. 2 отдельных запроса
 
Сверху