Всем привет! В этом уроке я расскажу о создании SQLite базы данных для своего сервера. 1. Вообще что такое SQLite ?
SQLite - легкая (не требующая больших ресурсов) встраиваемая система управления базами данных. Работа системы построена по принципу клиент-сервер. SQLite хранит всю базу данных в единственном стандартном файле (.db) на том компьютере, на котором исполняется программа.
Все данный в базе представлены в виде таблиц, элементами которых являются:
rows - строки
fields - столбцы (поля)
У каждого столбца есть свое имя, но кроме имени они имеют и свой порядковый номер (начиная с нуля)
Ниже дан пример таблицы players которая используется на моем сервере:
Рисунок 1 - пример таблицы SQL
Здесь можно увидеть 7 столбцов. Заголовки: id, name, ip, pass, email, gang,lvl - это и есть имена столбцов. Столбец id я сделал специально для нумерации строк, а также эти значения являются уникальными для каждого аккаунта.
Строки имеют только номер, но средствами a_sampdb.inc нельзя найти строку по указанному номеру.
У каждого столбца есть свой тип данных, давайте рассмотрим некоторые из них:
INT - целые числа от -2 147 483 648 до 2 147 483 647
SMALLINT -целые числа от -32 768 до 32 767
TINYINT - целые числа от -128 до 127
VARCHAR - строки переменной длинны (НЕ длиннее 255 символов)
Этих типов достаточно для создания системы аккаунтов.
2. Как управлять своей базой данных ?
Напомню, еще раз, что система управления базами данных SQLite УЖЕ встроена в SAMP-сервер и никаких плагинов для ее работы подключать НЕ надо!
Вот как реализуется система клиент-сервер:
При запуске SAMP-сервера из вашего игрового мода происходит обращение к SQLite базе данных, ваш мод выступает в роли клиента, а SAMP-сервер это и есть SQLite-сервер. Обращения мода представляют собой текстовые запросы на специальном языке который понятен SQLite-серверу. В ответ на запрос приходит результат, который можно представить в виде переменной DBResult
Code
new DBResult:TEST; // объявляем переменную типа "Результат запроса"
Эта переменная представляет собой двумерный массив (небольшую таблицу) который получился отбрасыванием ненужной информации от главной (большой) таблицы.
3. Язык SQL запросов
Этот язык на самом деле очень простой, и только на первый взгляд кажется ужасным майндфаком
Чтобы понять с чего начинать писать запрос надо понять чего мы хотим получить, итак:
(смотрите картинку выше)
3.1 SELECT
Если требуется выбрать (получить) данные из таблицы используем слово SELECT, после него ставим пробел и указываем столбцы из которых хотим получить данные:
Если хотим получить только один столбец: SELECT name
Если несколько (разделяем запятой): SELECT name, pass, gang
Если все столбцы (ставим звездочку): SELECT *
Все, со столбцами определились, далее пишем слово FROM - это слово указывает из какой таблицы требуется выбрать столбцы. Добавляем имя таблицы, например players
Делее следует слово WHERE с помощью него задаются особые условия выборки:
Например требуется получить имена и IP-адреса игроков уровень которых больше 30:
SELECT name, ip FROM players WHERE lvl > 30
Рисунок 2 - логика выборки SELECT
Надо получить пароль игрока имя которого OFFREAL:
SELECT pass FROM players WHERE name = 'OFFREAL'
Рисунок 3 - логика выборки SELECT Примечание: если в запросе используются текстовые данные (VARCHAR см. выше), их следует заключать в одинарные кавычки 'текст'
Схема запроса:
SELECT имя_столбца_А, имя_столбца_Б, ... FROM имя_таблицы WHERE условия LIMIT N
Примечание: Слово LIMIT указывает сколько строк данных будет в результате, N - количество строк

3.2 UPDATE
Если требуется обновить данные в таблице, запрос начинается со слова UPDATE после которого указывается имя таблицы (например players) Далее следует слово SET и перечисляются данные которые надо изменить. Заканчивается запрос особым условием WHERE (см. описание выше)
Пример: требуется сохранить уровень игрока OFFREAL равный 25
UPDATE players SET lvl = 25 WHERE name = 'OFFREAL'
Требуется сохранить новый E-mail игрока Crysis - crysis@mail.ru
UPDATE players SET email = 'crysis@mail.ru' WHERE name = 'Crysis'
Примечание: Результат запроса UPDATE - простое число, равное 0 если запись в базу не произошла, 1 если все прошло успешно.
Схема запроса:
UPDATE имя_таблицы SET имя_столбца_А = новые_данные_в_столбец_А, имя_столбца_Б = новые_данные_в_столбец_Б, ...WHERE условия

3.3 INSERT INTO
Используется для добавления новой строки, пишем: INSERT INTO, далее имя таблицы (например players), далее в скобках перечисляем столбцы в которые вносим данные: ( name, ip, lvl ), далее пишем слово VALUES и в скобках перечисляем значения переменных: ( 'MySAN', '92.38.226.4', 35 ). Целиком получилось:
INSERT INTO players ( name, ip, lvl ) VALUES ( 'MySAN', '92.38.226.4', 35 )
Мы внесли в таблицу players информацию о игроке MySAN, IP-адрес которого 92.38.226.4, уровень которого 35
Схема запроса:
INSERT INTO имя_таблицы ( имя_столбца_А, имя_столбца_Б, ... ) VALUES ( значение_в_столбец_А, значение_в_столбец_Б, ...)
Примечания:
- При создании нового аккаунта, я рекомендую в INSERT INTO заполнять все столбцы, так как при попытке выбора (SELECT) целочисленных (INT, BIGINT ...)данных из пустых ячеек SAMP-сервер падает!
- Будьте аккуратнее при оформлении запросов с указанием текстовых (VARCHAR) переменных, если забыть поставить ' это приведет к образованию пустых ячеек во всей строке!

3.4 CREATE TABLE
Этот запрос применяется крайне редко для создания таблиц, тут все предельно просто, пишем:
CREATE TABLE, далее имя для новой таблицы, пусть будет vehicles, далее ставим скобки ( ), внутри которых через запятую перечисляем по порядку имена бушующих столбцов и тип данных в них (см. выше):
CREATE TABLE vehicles ( model int, x_coord varchar, y_coord varchar, z_coord varchar, a_coord varchar )
Схема запроса:
CREATE TABLE имя_новой_таблицы ( имя_столбца_0 тип_данных_в_столбце_0, имя_столбца_1 тип_данных_в_столбце_1, ... )
Примечания:
- не важно строчными или заглавными буквами указывать тип переменных
- имя таблицы или столбца не должно начинаться с цифр
4. Функции a_sampdb.inc
Давайте ознакомимся с функциями для управления базой данных:
db_open(name[]);
- функция используется для подключения к базе данных. В качестве name указывается путь к файлу .db и его имя. В случае успешного подключения эта функция вернет нам значение особой переменной типа DB
Code
#define DBNAME "MyDATA.db" // определяем неизменное имя файла ...
// ... базы данных, если назвать его так, то он будет находиться в папке scriptfiles ...
// ... Можно было написать и так: new DBNAME = "MyDATA.db";
new DB:ServerDB; // объявляем особую переменную типа Data Base
Далее пишем в OnGameModInit
Code
OnGameModInit()
{
ServerDB = db_open(DBNAME); // подключаемся к базе данных, ...
// ... особая переменная ServerDB теперь определена, подключение выполнено!
// продолжение паблика OnGameModInit
return 1;
}
db_close(DB:db);
- функция используется для отключения от базы данных
Code
OnGameModExit()
{
db_close(ServerDB);
// продолжение паблика OnGameModExit
return 1;
}
Примечания: Я рекомендую не отключаться от базы данных на протяжении всей работы мода!
db_query(DB:db,query[]);
- функция используется для отправки запросов (см. пункт 3) SQLite-серверу, и возвращает результат в виде особой переменной DBResult. Все следующие функции которые мы рассмотрим будут работать именно с DBResult.
Code
new DBResult:TEST; // объявляем переменную типа DBResult ( Результат запроса )
TEST = db_query(ServerDB, "SELECT id, name, pass FROM players WHERE lvl = 40"); //
В результате содержится двумерный массив в котором 3 столбца и 4 строки (см. рисунок ниже)
Рисунок 4 - зеленым отмечены ячейки выбранные из таблицы, это и есть результат
А вот как выглядит сам результат запроса:
Рисунок 5 - результат запроса
db_free_result(DBResult:dbresult);
- функция очищает результат запроса
Code
db_free_result(TEST); // в TEST теперь нет ни строк ни столбцов =(
db_num_rows(DBResult:dbresult);
- функция проверяет результат запроса и возвращает количество строк в нем
Code
new DBResult:TEST; // объявляем переменную типа DBResult ( Результат запроса )
TEST = db_query(ServerDB, "SELECT id, name, pass FROM players WHERE lvl = 40"); //
// В результате содержится двумерный массив в котором 3 столбца и 4 строки (см. рисунок 5)
new Nrows = db_num_rows(TEST); // Nrows будет равно 4
printf("Я нашел %d строк в результате запроса!", Nrows); // выводим сообщение в ...
//консоль сервера. Там будет следующее: "Я нашел 4 строк в результате запроса!"
db_num_fields(DBResult:dbresult);
- функция проверяет результат запроса и возвращает количество столбцов в нем
Code
new DBResult:TEST; // объявляем переменную типа DBResult ( Результат запроса )
TEST = db_query(ServerDB, "SELECT id, name, pass FROM players WHERE lvl = 40"); //
// В результате содержится двумерный массив в котором 3 столбца и 4 строки (см. рисунок 5)
new Nfield = db_num_fields(TEST); // Nfield будет равно 3
printf("Я нашел %d столбцов в результате запроса!", Nfield); // выводим сообщение в ...
//консоль сервера. Там будет следующее: "Я нашел 3 столбцов в результате запроса!"
db_next_row(DBResult:dbresult);
- функция для переключения на следующую строку результата. Сразу после того как мы получили результат запроса, он переключен на 0 строку (см. рисунок 5), и теперь указав номер (или имя) столбца мы получим значение ячейки.
Например указав столбец номер 1 мы получим значение ячейки 'OFFREAL', а если потом один раз воспользуемся db_next_row, то получим '[EPG]_KOT'
Примечания: Чаще всего в запросах SELECT (см. выше) используют LIMIT 1 и получают результаты в которых только одна строка, но много столбцов, и использовать db_next_row не приходится.
db_field_name(DBResult:dbresult, field, result[], maxlength);
- функция возвращает имя столбца результата запроса по номеру этого столбца
Code
new f_name[32]; // объявляем переменную для имени столбца
new DBResult:TEST; // объявляем переменную типа DBResult ( Результат запроса )
TEST = db_query(ServerDB, "SELECT id, name, pass FROM players WHERE lvl = 40");
db_field_name(TEST, 2, f_name, 32); // хотим узнать имя 2 столбца результата запроса
printf("Имя второго столбца: %s",f_name); // выводим сообщение в консоль сервера
// там будет: "Имя второго столбца: pass"
db_get_field(DBResult:dbresult, field, result[], maxlength);
и
db_get_field_assoc(DBResult:dbresult, const field[], result[], maxlength);
- функции получают значение ячейки которая находится на пересечении указанного в них столбца (field или field[]) и строки на которую переключен результат запроса (см. описание db_next_row).
В db_get_field указывается номер столбца (параметр field),
а в db_get_field_assoc указывается имя столбца (параметр field[])
Пример: требуется получить значение ячейки (строка 0, столбец 1) результата запроса
Code
new DBResult:TEST; // объявляем переменную типа DBResult ( Результат запроса )
TEST = db_query(ServerDB, "SELECT id, name, pass FROM players WHERE lvl = 40"); // рисунок 5
// результат сейчас переключен на 0 строку
new var[128]; // объявляем переменную, которая примет значение ячейки (столбец 1, строка 0)
db_get_field(TEST, 1, var, 128); // получаем значение ячейки (столбец 1, строка 0)
printf("Значение ячейки: %s", var); // выводим в консоль сервера
// там будет: "Значение ячейки: OFFREAL"
Примечания: Эти две функции получают содержимое ячеек только в текстовом формате, и для преобразования некоторых значений в числа надо использовать strval или floatstr
5. Создание и редактирование базы данных
Новую базу данных можно создать либо используя функции a_sampdb.inc, либо с помощью редактора баз данных.
Сначала я расскажу как создать базу данных в редакторе:
5.1 Создаем в редакторе!
Работать будем с программой SQLite Database Browser, она бесплатная и доступна для скачивания на сайте разработчика.
[ Скачать! ]
1. Извлекаем файлы из ZIP-архива, и запускаем программу.
Рисунок 6 - Окно программы SQLite Database Browser 2. Жмем [ File ] > [ New Database ]
3. Появляется окно Save As, надо выбрать папку и имя для файла базы данных:
- выбираем папку: scriptfiles (в папке с вашим samp-сервером)
- вводим имя файла: MyDATA.db
- жмем [ Сохранить ]
4. Появляется окно Create Table, программа предлагает создать первую таблицу в новой базе данных
Рисунок 7 - Процесс создания таблиц и столбцов
1 Create Table
- Table name: Вводим имя новой таблицы, пусть будет players
- Жмем [ Add ], для добавления новых столбцов в таблицу.
2 Add database field
- Field name: Вводим имя для первого столбца таблицы, пусть будет name (для сохранения никнейма игрока)
- Жмем [ ... ], для выбора типа данных в столбце
3 Enter field type
- Указываем тип данных VARCHAR (текстовый тип данных)
- Жмем [ OK ]
2 Add database field
- Жмем [ Create ] Аналогично добавляем в таблицу еще 2 столбца, пусть это будут столбцы level и money. Не забываем, что тип данных для level и money следует выбрать INT (целые числа)
После добавления столбцов, жмем кнопку [ Create ] в окне Create Table
5. Сохраняем базу данных [ File ] > [ Save Database ]
Если вы все сделали правильно, то должны увидеть это:
Рисунок 8 - База данных с пустой таблицей Примечания:
- Для добавления новой строки жмем кнопку [ New Record ]
- Во вкладке [ Database Structure ] можно просмотреть структуру базы данных и таблиц, а во вкладке [ Execute SQL ] можно написать и отправить SQL-запрос к базе данных и просмотреть полученный результат
Итак, с помощью программы SQLite Database Browser мы создали базу данных MyDATA.db, добавили в нее новую таблицу players и сохранили на жесткий диск. А теперь научимся создавать новую базу данных с помощью функций a_sampdb.inc
5.2 Создаем с помощью a_sampdb.inc! Вообще, этот способ создания новой базы данных был обнаружен мною случайно
Оказывается, если попытаться подключиться к базе данных которой не существует, то встроенный SQLite-сервер сам ее создаст...
1. Вводим глобальные переменные:
Code
new DB:ServerDB; // объявляем особую переменную типа Data Base
#define DBNAME "MyDATA.db" // определяем имя файла базы данных
2. Добавляем в OnGameModInit проверку:
- Если файл базы данных есть, то просто подключаемся к базе
- Если файла базы данных нет, то создаем его и добавляем в него таблицу (см. пункт 3.4)
Code
public OnGameModeInit()
{
if(fexist(DBNAME)) // файл базы данных на месте
{
ServerDB = db_open(DBNAME); // просто открываем
}
else // нет файла базы данных!
{
ServerDB = db_open(DBNAME); // создаем файл базы данных ...
db_query(ServerDB, "CREATE TABLE players (name varchar, level int, money int)");
// ... и создаем такую-же таблицу как создали с помощью SQLite Database Browser
}
// продолжение паблика OnGameModeInit
return 1;
}
Вот и вся магия
база данных MyDATA.db с пустой таблицей players создана в папке scriptfiles
5.3 Про доступ к файлу базы данных Что будет если редактировать базу данных через стороннюю программу при работающем samp-сервере?
Я несколько раз проверял, и не обнаружил никаких проблем в работе сервера.
Обычно, если редактировать и сохранять один файл одновременно в двух программах, то конечный файл будет таким каким был при последнем сохранении в одной из программ. В случае с базой данных SQLite этого НЕ происходит!
6. Создание простой системы аккаунтов
Теперь мы знаем все необходимое для создания системы аккаунтов, приступим к делу:
Пусть у нас есть таблица:
CREATE TABLE players (name varchar, pass varchar, level int, money int)
Не забываем про глобальные переменные:
Code
new DB:ServerDB;
#define DBNAME "MyDATA.db"
И про подключение к базе в OnGameModInit:
Code
public OnGameModeInit()
{
if(fexist(DBNAME)) // файл базы данных на месте
{
ServerDB = db_open(DBNAME); // просто открываем
}
else // нет файла базы данных!
{
ServerDB = db_open(DBNAME); // создаем файл базы данных ...
db_query(ServerDB, "CREATE TABLE players (name varchar, pass varchar, level int, money int)");
// ... и создаем такую-же таблицу как создали с помощью SQLite Database Browser
}
// продолжение паблика OnGameModeInit
return 1;
}
6.1 Подключение игрока к серверу Для начала нам потребуется набор глобальных переменных, для хранения информации о игроке:
Code
enum pl_info {
P_reg,
P_name[32],
P_pass[32],
P_level,
P_money
}
new PlayerInfo[MAX_PLAYERS][pl_info];
Паблик OnPlayerConnect:
Code
public OnPlayerConnect(playerid)
{
PlayerInfo[playerid][P_reg] = 0; // не вошел на сервер
new DBResult:RESCONN; // результат запроса
new query[128]; // переменая для строки запроса
GetPlayerName(playerid, PlayerInfo[playerid][P_name], 32); // получаем имя игрока
format(query,sizeof(query),"SELECT * FROM players WHERE name = '%s' LIMIT 1", PlayerInfo[playerid][P_name]);
// запросили данные игрока
RESCONN = db_query(ServerDB,query); // отправляем запрос
if(!db_num_rows(RESCONN)) // если имени игрока нет в таблице
{
format(query,sizeof(query),"Привет, %s!\nСначала зарегистрируйся!\nВведи свой пароль ниже:",PlayerInfo[playerid][P_name]);
ShowPlayerDialog(playerid, 51, 1, "REGISTER", query, "Далее", "Выход"); // отправили диалог с ИД 51
}
else // а если есть
{
new var[32];
db_get_field(RESCONN, 1, PlayerInfo[playerid][P_pass], 32); // вытащили пароль игрока из таблицы
db_get_field(RESCONN, 2, var, 32); PlayerInfo[playerid][P_level] = strval(var); // вытащили уровень
db_get_field(RESCONN, 3, var, 32); PlayerInfo[playerid][P_money] = strval(var); // вытащили деньги
format(query,sizeof(query),"Добро пожаловать, %s!\nВведите ваш пароль:",PlayerInfo[playerid][P_name]);
ShowPlayerDialog(playerid, 50, 1, "LOGIN", query, "Вход", "Выход"); // отправили диалог с ИД 50
}
// продолжение паблика OnPlayerConnect
return 1;
}
Диалоги:
Code
public OnDialogResponse(playerid, dialogid, response, listitem, inputtext[])
{
if(dialogid == 50) // диалог LOGIN
{
if(!response) // нажал кнопку "Выход"
{ Kick(playerid); return 1; }
else // нажал кнопку "Вход"
{
if(!strlen(inputtext)) // не ввел пароль
{
ShowPlayerDialog(playerid, 50, 1, "LOGIN", "Забыл ввести пароль!\nВводи сюда:", "Вход", "Выход");
return 1;
}
if(!strcmp(inputtext, PlayerInfo[playerid][P_pass], false)) // если верный пароль
{
SendClientMessage(playerid, 0xFFFFFFFF, "Вы успешно вошли");
SetPlayerScore(playerid, PlayerInfo[playerid][P_level]); // поставили уровень
GivePlayerMoney(playerid, PlayerInfo[playerid][P_money]); // дали денег
PlayerInfo[playerid][P_reg] = 1; // вошел на сервер (залогинился)
}
else // если неверный
{
SendClientMessage(playerid, 0xFFFFFFFF, "Вы ввели неверный пароль и были кикнуты с сервера");
Kick(playerid);
}
return 1;
}
}
if(dialogid == 51) // диалог REGISTER
{
if(!response) // нажал кнопку "Выход"
{ Kick(playerid); return 1; }
else // нажал кнопку "далее"
{
if(strlen(inputtext) < 3 || strlen(inputtext) > 16) // слишком короткий или слишком длинный пароль
{
ShowPlayerDialog(playerid, 51, 1, "REGISTER", "Пароль должен быть не длиннее 16 и не короче 3 символов\nВведи свой пароль:", "Далее", "Выход");
return 1;
}
// проверяем пароль на допустимые символы
new allowed = 1;
for (new i=0;i<strlen(inputtext);i++)
{
if (inputtext[i]==0) { allowed = 1; break; } // null
if ((inputtext[i]<48) && (inputtext[i]!=32)) { allowed = 0; break;}
if (inputtext[i]>57 && inputtext[i]<65) { allowed = 0; break;}
if (inputtext[i]>90 && inputtext[i]<97) { allowed = 0; break;}
if (inputtext[i]>122) { allowed = 0; break;}
}
// конец проверки
if(!allowed) // есть недопустимые символы
{
ShowPlayerDialog(playerid, 51, 1, "REGISTER", "Недопустимые символы в пароле, используйте a-z, A-Z, 0-9\nВведи свой пароль:", "Далее", "Выход");
return 1;
}
new query[128]; // переменая для строки запроса
GetPlayerName(playerid, PlayerInfo[playerid][P_name], 32); // получаем имя игрока
format(query,sizeof(query),"INSERT INTO players ( name, pass, level, money ) VALUES ( '%s', '%s', 0, 0)", PlayerInfo[playerid][P_name], inputtext); // добавить новую запись в таблицу
db_query(ServerDB,query); // отправили запрос
PlayerInfo[playerid][P_reg] = 1; // залогинился
SendClientMessage(playerid, 0xFFFFFFFF, "Аккаунт создан!");
PlayerInfo[playerid][P_level] = 0;
PlayerInfo[playerid][P_money] = 0;
return 1;
}
}
return 0;
}
6.2 Выдача score, money и прочего... Так как в пункте 6.1 мы ввели глобальную переменную PlayerInfo, она должна соответствовать действительному значению score, money и прочего. Поэтому везде, кроме диалога LOGIN рекомендую использовать модифицированные функции:
Code
stock XSetPlayerScore(playerid, score)
{
PlayerInfo[playerid][P_level] = score;
SetPlayerScore(playerid, score);
}
stock XGivePlayerMoney(playerid, money)
{
PlayerInfo[playerid][P_money] += money;
GivePlayerMoney(playerid, money)
}
6.3 Игрок покидает сервер Когда игрок выходит с сервера, надо сохранять его прогресс.
Code
public OnPlayerDisconnect(playerid)
{
if(PlayerInfo[playerid][P_reg] == 1) // если был залогинен
{
new query[128]; // переменая для строки запроса
GetPlayerName(playerid, PlayerInfo[playerid][P_name], 32); // получаем имя игрока
format(query,sizeof(query),"UPDATE players SET level=%d, money=%d WHERE name='%s'", PlayerInfo[playerid][P_name], PlayerInfo[playerid][P_level], PlayerInfo[playerid][P_money]); // обновить данные в таблице
db_query(ServerDB,query); // отправили запрос
}
// продолжение паблика OnPlayerDisconnect
return 1;
}
- - - - - - - - - - - - - - - - - -
Задавайте свои вопросы по этой теме, я постараюсь на них ответить!