Jump to content

[Урок] Встроенные функции для работы с MySQL / SQLite


Recommended Posts

Знаете что-нибудь о MySQL или SQLite? Если нет, сходите и почитайте.

Все, кто в танке хотя бы раз пользовались базами данных (БД) для хранения каких-либо своих (или чужих) данных. MySQL - думаю, все знают и юзают, SQLite - реже, но тоже неплохой вариант. До недавнего времени приконектится к MySQL серверу можно было только с помощью серверного модуля, и создавать свои собственные SQLite базы данных (БД) в МТА было невозможно. Но благодаря самым активным разработчикам МТА, все эти неудобства ушли в историю. Теперь мы имеем встроенные функции для работы с MySQL / SQLite. О них и пойдет речь.

Для работы с БД нам нужны только эти функции:

  • dbConnect - приконектится к базе данных / открыть базу данных
  • dbExec - выполнить запрос без возврата результата
  • dbQuery - выполнить запрос с возвратом результата
  • dbPoll - получить результат запроса из памяти
  • dbFree - удалить результат запроса из памяти
  • destroyElement - закрыть соединение с базой данных / закрыть базу данных

Детальное описание функций будет внизу, сначала просмотрите примеры.

Сразу даю небольшие разъяснения:

  • Ссылка (на соединение или БД), которую возвращает функция dbConnect (при успехе) - это элемент. Поэтому, чтобы закрыть соединение (закрыть БД) нужно юзать destroyElement.
  • Ссылка, которую возвращает функция dbQuery (при успехе) - это не элемент, это просто уникальный ИД.
  • Много открытий и закрытий БД (соединений) будут ощутимо тормозить ваш сервер, поэтому желательно открывать БД (соединение) при старте ресурса, а закрывать - при остановке ресурса. А саму ссылку на БД можно сохранить в глобальной переменной, например, тогда все скрипты ресурса смогут ее использовать.
  • Если вы плохо знаете SQL синтаксис, сначала подучите его до уровня "выше среднего" во избежание глупых ошибок.

MySQL


  • Чтобы быстро понять способы работы с этими функциями, начнем с примеров. Допустим, у нас рядом с сервером на том же хосте (локальный IP - 127.0.01) запущен MySQL сервер на 54321 порту. Имя юзера для доступа - testUser, пароль для этого юзера - testPassword, а имя БД - testDB.
    Коннектимся к MySQL серверу ( dbConnect )
-- создаем глобальную переменную для хранения ссылки на коннект 
sqlLink = dbConnect ( 
    "mysql", -- тип базы данных 
    "host=127.0.0.1;port=54321;dbname=testDB", -- хост, порт, имя БД 
    "testUser", -- юзер 
    "testPassword" -- пароль юзера 
) 


Выполняем запросы, результаты которых нам не нужны ( dbExec )
Тексты запросов:

CREATE TABLE IF NOT EXISTS testTable ( 
  id INT NOT NULL AUTO_INCREMENT PRIMARY KEY, 
  name VARCHAR(32), 
  password VARCHAR(24), 
  money INT 
); 
  
INSERT INTO testTable ( name, password, money ) 
  VALUES ( 'MX_Master', '1245', 1500 ); 


-- создаем таблицу 
dbExec ( 
    sqlLink, -- ссылка на коннект с базой 
  
    -- запрос 
    -- [[ и ]] это просто кавычки (в языке LUA) для многострочных строк  
    [[CREATE TABLE IF NOT EXISTS ?? ( 
        ?? INT AUTO_INCREMENT PRIMARY KEY, 
        ?? VARCHAR(32), 
        ?? VARCHAR(24), 
        ?? INT );]], 
  
    -- параметры, которые будут подставлены в запрос вместо знаков "??". 
    -- если юзать "?" вместо "??", то все строки будут экранированы и с кавычками 
    "testTable", -- заменяет первый знак "??" 
    "id", -- заменяет второй знак "??" 
    "name", -- заменяет третий знак "??" 
    "password", -- заменяет четвертый знак "??" 
    "money" -- заменяет пятый знак "??" 
) 


-- добавляем одну запись 
dbExec ( 
    sqlLink, -- ссылка на коннект с базой 
  
    -- запрос 
    [[INSERT INTO testTable ( name, password, money ) 
          VALUES ( ?, ?, ? );]], 
  
    -- параметры, которые будут подставлены в запрос вместо знаков "?" 
    "MX_Master", 
    "1245", 
    1500 
) 


Сделаем запрос, результат которого нам пригодится ( dbQuery, dbPoll, dbFree ). Будем юзать колбэк функцию, которая должна сама выполнится когда результат запроса будет готов

-- делаем выборку 
dbQuery ( 
    -- это одноразовая функция, которая вызовется, когда выполнится запрос 
    function ( queryHandle ) -- queryHandle это уникальный ИД результата запроса 
        -- получим результат запроса 
        local resultTable, num, err = dbPoll( queryHandle, 0 ) 
  
        -- проверим результат 
        if resultTable then 
            outputChatBox('Запрос выполнен:') 
  
            for rowNum, rowData in ipairs(resultTable) do 
                outputChatBox( '  запись '..rowNum..': '..rowData['id']..', '..rowData['name']..', '..rowData['password']..''..rowData['money']) 
            end 
        elseif resultTable == nil then 
            outputChatBox('Запрос еще не выполнен.') 
            dbFree(queryHandle) -- очистим память от результата 
        elseif resultTable == false then 
            -- num в данном случае код ошибки, а err это описание ошибки 
            outputChatBox('Ошибка в запросе, код '..num..': '..err) 
        end 
    end, 
  
    sqlLink, -- ссылка на коннект с базой 
  
    -- запрос 
    "SELECT * FROM testTable;" 
) 


если все правильно, то результат будет примерно такой

Запрос выполнен: 
  запись 1: 1, MX_Master, 1245, 1500 


Закроем соединение ( destroyElement )

destroyElement( sqlLink ) 


SQLite


  • Для SQLite многое аналогично MySQL, но открывать БД нужно слегка иначе. Вместо хоста нужно указывать путь к файлу БД. Если путь начинается с ":/" (например ":/public.db"), то файл БД будет открыт/создан в общей папке сервера для SQLite БДшек. Если путь начинается с ":ИМЯ_РЕСУРСА/" (например ":play/test.db"), то файл БД будет открыт/создан в папке указанного ресурса. В остальных случаях файл БД будет открыт/создан в папке текущего ресурса, в котором выполняется скрипт.
    Заметка: если файл БД не существует, то он будет создан.
    Открываем файл БД ( dbConnect )
-- создаем глобальную переменную для хранения ссылки на коннект 
sqlLink = dbConnect ( 
    "sqlite", -- тип базы данных 
    "test.db" -- путь к файлу БД 
) 


Выполняем запросы, результаты которых нам не нужны ( dbExec )
Тексты запросов:

CREATE TABLE IF NOT EXISTS testTable ( 
  id INTEGER AUTOINCREMENT PRIMARY KEY, 
  name TEXT, 
  password TEXT, 
  money INTEGER 
); 
  
INSERT INTO testTable ( name, password, money ) 
  VALUES ( 'MX_Master', '1245', 1500 ); 


-- создаем таблицу 
dbExec ( 
    sqlLink, -- ссылка на коннект с базой 
  
    -- запрос 
    -- [[ и ]] это просто кавычки (в языке LUA) для многострочных строк  
    [[CREATE TABLE IF NOT EXISTS testTable ( 
        id INTEGER AUTOINCREMENT PRIMARY KEY, 
        name TEXT, 
        password TEXT, 
        money INTEGER );]] 
) 


-- добавляем одну запись 
dbExec ( 
    sqlLink, -- ссылка на коннект с базой 
  
    -- запрос 
    [[INSERT INTO testTable ( name, password, money ) 
          VALUES ( 'MX_Master', '1245', 1500 );]] 
) 


Чтобы не повторяться в этот раз сделаем запрос и обойдемся без колбэк функции ( dbQuery, dbPoll, dbFree )

local queryHandle = dbQuery( 
    sqlLink, -- ссылка на коннект с базой 
  
    -- запрос 
    "SELECT * FROM testTable;" 
) 
  
-- ждем результата (сервер в это время простаивает) 
local resultTable, num, err = dbPoll ( queryHandle, -1 ) 
  
-- проверим результат 
if resultTable then 
    outputChatBox('Запрос выполнен:') 
     
    for rowNum, rowData in ipairs(resultTable) do 
        outputChatBox( '  запись '..rowNum..': '..rowData['id']..', '..rowData['name']..', '..rowData['password']..''..rowData['money']) 
    end 
elseif resultTable == nil then 
    outputChatBox('Запрос еще не выполнен.') 
    dbFree(queryHandle) -- очистим память от результата 
elseif resultTable == false then 
    -- num в данном случае код ошибки, а err это описание ошибки 
    outputChatBox('Ошибка в запросе, код '..num..': '..err) 
end 
  


если все правильно, то результат будет примерно такой

Запрос выполнен: 
  запись 1: 1, MX_Master, 1245, 1500 


Закроем файл БД ( destroyElement )

destroyElement( sqlLink ) 


Детальное описание функций

  • dbConnect ( "типБазыДанных", "хост" [, "имяЮзера", "парольЮзера" [, "опции" ] ] ) 
      
    "типБазыДанных" -- "mysql" или "sqlite" 
    "хост" -- для SQlite это путь к файлу БД, для MySQL строка вида "host=0.0.0.0;port=00000;dbname=ИМЯ_БД" 
    "имяЮзера" -- только для MySQL 
    "парольЮзера" -- только для MySQL 
    "опции" -- только для опытных скриптеров, знающих английский язык (: остальные - не юзайте ЭТО 
      
    -- функция возвращает [b]элемент[/b] или [b]false[/b] при ошибке 
    


  • dbExec
  • dbQuery
  • dbPoll
  • dbFree
  • destroyElement

Туториал еще не закончен, и постепенно увеличивается (:

некоторые примеры еще не проверены

задавайте интересующие вопросы

если нашли ошибку - укажите её

testPassword

 

-- ждем результата (сервер в это время простаивает)

local resultTable, num, err = dbPoll

Edited by Guest
Link to comment
Просто шикарный туториал, от хорошего модератора!

Лесть.

Ну и на кой хрен это встраивать в сервер было? Зачем лишнее утяжеление? В виде отдельного плагина хорошо же существовало, нет?

В плагине подключение не выдает элемент, как в стандартной системе, это пока единственное что я заметил. И в плагине большое количество ф-ий (которые наверно можно компенсировать стандартными, правда я не знаю как).

Link to comment
Ну и на кой хрен это встраивать в сервер было? Зачем лишнее утяжеление? В виде отдельного плагина хорошо же существовало, нет?

АНАЛОГИЧНЫЙ ВОПРОС

Зачем на детский велосипед ставить третье колесо? Зачем эти навороты? Пускай дети на двухколёсном ездят как все. А если что, можно прикрутить дополнительные 2 мини колёса, разве нет?

Преимущество в том, что работать с результатами выборки, можно сразу как с LUA таблицей безо всяких "fetch"ей. Ну и во-вторых - зачем куча вспомогательных функций, если все можно делать 4-5 функциями. Не забывайте, что эта возможность не только для MySQL добавлялась, а чтобы еще была возможность создавать и править свои SQLite базы!

Link to comment
Ну и на кой хрен это встраивать в сервер было? Зачем лишнее утяжеление? В виде отдельного плагина хорошо же существовало, нет?

Мне вообще не понравились эти функции как для работы MySQL.. Больно они не удобные.. Привычнее использовать плагин от ryden (https://wiki.multitheftauto.com/wiki/Mysql)

Link to comment

Да, их вид, названия и параметры не совсем привычны для тех, кто, например, юзал мускул функции в РНР.

После завершения туториала, попробуйте поюзать плагин и встроенные функции - сравнить по объему и читабельности коды с плагином и без плагина.

Link to comment

Уже.. И выбрал MySQL плагин. Читабельность привычнее. Объём кода как бы не сильно больше, да и это не потеря (к тому же можно написать свои классы ддя работы).

Link to comment

класс для работы можно создать для любой системы. Я так понял, что встроенные функции нужны для тех, кому не до плагинов, или плагин кажется более сложным. Однако, нет плагина, который бы добавил возможность юзать свои SQLite БДшки.

Link to comment

Можно подробно все с комментариями?

SQL:

SELECT * FROM `users` WHERE `id` = '1' 

В MySQL модуле это mysql_fetch_assoc, что возвращает таблицу, например, значение password из выборки получается так:

local data = mysql_fetch_assoc(query) 
  
--data['password'] - password из выборки. 

А тут как?

Link to comment

ща проверю на эскуле.. мускул не охота подымать..

вощем dbPoll возвращает общую таблицу, состоящую из подтаблиц. Каждая подтаблица это и есть одна запись.

в первом посте подправил пример, чтобы стало еще понятнее

Link to comment

кстати, я пробовал и у меня колбэк функция при выполненном запросе, хоть убей, не вызывается. А если без колбэк функции сразу за запросом dbPoll заюзать, то все пучком.

dbPoll вне колбэк функции может вернуть три разных значения, "результат не готов" (nil), "ошибка в запросе" (false) или "таблицу с результатом" (table). Внутри колбэк функции "результат не готов" (nil) не может быть, потому что результат или уже готов, раз вызвалась колбэк функция, или "ошибка в запросе" (false) была.

У dbPoll второй параметр это задержка в милисекундах. Он нужен только, если функция вызывается вне колбэк функции. Если указать -1, то сервер будет ждать пока запрос выполнится и только потом продолжит что-то делать. Если указать задержку 0, то сервер сразу же запросит результаты запроса, что обычно приведет вне колбэк функции к nil, т.к. результат будет еще не готов. Если указать значение от 1 и более. то сервер в течении этого времени будет простаивать и ждать результата не более указанных милисекунд и потом, даже если не готов результат, продолжит что-то делать дальше.

Link to comment
Можно тоже самое с кодом? Не очень дошло до меня :)

на вики примеры просматривал? там есть пример, где dbPoll используется следом за dbQuery

local qh = dbQuery( connection, "SELECT * FROM table_name" ) 
local result = dbPoll( qh, -1 ) 

на второй строчке сервер останавливается и принудительно ждет пока выполнится запрос, сколько бы времени это не заняло. После получения любого результата, работа сервера продолжается и сервер переходит на следующие строки ниже.

local qh = dbQuery( connection, "SELECT * FROM table_name" ) 
local result = dbPoll( qh, 0 ) 

на второй строчке сервер ничего не ждет, а сразу получает результат, который скорее всего будет nil, т.к. на многие запросы нужно время. В !!редких!! случаях запрос может успеть выполниться, но я бы не надеялся на это. Лучше юзать 0 в качестве второго параметра dbPoll только внутри колбэк функции (ее можно указать в dbQuery).

local qh = dbQuery( connection, "SELECT * FROM table_name" ) 
local result = dbPoll( qh, 500 ) 

на второй строчке сервер останавливается и принудительно ждет результата максимум 500 милисекунд. Если результата нет более чем 500 мс, то результат будет nil и сервер переходит на следующие строки ниже. Результат может быть готов и раньше чем через 500 мс, в этом случает сервер раньше переходит на следующие строки ниже.

Link to comment
Не до конца понял: сервер заснет на время запроса полностью или заснет только в работе с самой БД?

Полностью весь сервер приостановится, если заставить его ждать результата вне колбэк функции с помощью dbPoll( handle, -1 ). Поэтому юзать колбэк функцию в dbQuery предпочтительней.

Link to comment
dbQuery( function(qh, tag, score) 
            local result = dbPoll( qh, 0 )   -- Timeout doesn't matter here because the result will always be ready 
            outputDebugString( tag )         -- Prints "hello" 
            outputDebugString( score )       -- Prints 2000 
         end, {"hello",2000}, connection, "SELECT * FROM table_name" ) 

Вот это с Wiki пример. Меня интересует, в параметрах функции есть qh, tag, score. Откуда там все эти параметры? qh - это по идее линк на запрос, но ведь qh находится внутри dbQuery(...). Как тогда в функцию возвращается линк на запрос?

Если я правильно понял, то если в dbQuery есть функция, то ее первый параметр автоматически линк на запрос. Это верно?

И откуда tag, score? Что по ним передается и откуда?

Link to comment
Если я правильно понял, то если в dbQuery есть функция, то ее первый параметр автоматически линк на запрос. Это верно?

И откуда tag, score? Что по ним передается и откуда?

верно, первый параметр - линк на результат. Название можно дать любое этому параметру.

tag, score - это опциональные параметры, которые будут переданы в функцию, когда запрос выполнится. Здесь они просто для примера, чтобы показать работу механизма.

Link to comment
  • 3 years later...

Спасибо, долго искал гайд по майэскюэлю. Это все функции? Если нет, то где можно найти информацию(язык неважен). Хотелось бы функции для создания записей и их редактирования, а не только чтения :(

Link to comment
  • Other Languages Moderators
Спасибо, долго искал гайд по майэскюэлю. Это все функции? Если нет, то где можно найти информацию(язык неважен). Хотелось бы функции для создания записей и их редактирования, а не только чтения :(

Чтоб записывать и читать данные тебе хватит всего одной функции.

Link to comment

Не хватит, ибо тут только базовые принципы. Для начала подойдут эти справочники: MySQL и очень хороший по SQLite.

Кстати, заметил, что вывод таких функций, как count(*), будет находится в таком же виде и в результативной таблице, т.е. resulTable['count(*)'].

Link to comment

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...