Jump to content

Tarantool: use case and workaround


Recommended Posts

  • Other Languages Moderators
Posted (edited)

Part 0; Preamble

At the moment we have a long discussion about databases in the MTA Community, a huge part of the Team thinks that DB-drivers must be only as modules, but right now, we have only RDBMS databases(SQLite and MySQL). It isn’t enough for many dynamic MTA servers.

So, I want to suggest an interesting solution to this problem, so let’s get started!

 

Part 1; Tarantool

When we think about DODBMS the first variant of this is MongoDB. MongoDB works well with JS, like nodejs, but I want to present to you the DODBMS which work with Lua. 

This database is sometimes similar to MongoDB, but it works only with Lua(and SQL but I don’t get it). You can put the data there, get it, and write functions. Cool! And this DBMS is used in production, the developer of this DB is MailRu Group, so, take my word for it, these clients are serious guys, especially MasterCard.

Part 1.1: Setting and installing Tarantool

https://www.tarantool.io/en/download/os-installation/debian/

After installing launch the command `tarantool` and load global config with command  box.cfg{}

 

Part 1.2: The space’s markup

I'll try to make a managed alcohol list, so watch my hands

-- a sequence
box.schema.sequence.create('drinks_seq', { if_not_exists = true }) 
-- a space, like a table in MySQL
box.schema.create_space('drinks', { if_not_exists = true, format={

    { name = 'id', type = 'unsigned'},

    { name = 'name', type = 'string'},

    { name = 'count', type = 'unsigned'}}

}) 

 

-- an index
box.space.drinks:create_index('pk', { parts = { 'id' }, if_not_exists = true })
-- Insert 13 bottles of beer
box.space.drinks:insert{box.sequence.drinks_seq:next(), 'Beer', 13}

 

That’s all, the cascade has done!

 

Part 1.3: Fall in love workarounds

Without spoilers, our workaround is fetchRemote, so, do the server.

Any solution from: https://github.com/tarantool/http#installation

 

And take a look to my code:

#!/usr/bin/env tarantool
local http_router = require('http.router')
local http_server = require('http.server')
local json = require('json')

-- Load default config
box.cfg{}

-- Start the server
-- WARNING: 0.0.0.0 listen the port for all interfaces, don't do it, use 127.0.0.1!
local httpd = http_server.new('0.0.0.0', 3820, {
    log_requests = true,
    log_errors = true
})
local router = http_router.new()

-- GET endpoint, returns data from database
router:route({ path = '/drinks', method = 'GET' }, 
    function()
        -- Load space
        local dspace = box.space.drinks
        -- Select all, too risky read more about select and limits
        -- Here: https://www.tarantool.io/en/doc/latest/reference/reference_lua/box_index/select/
        local drinks = dspace:select{}
        return {status = 200, body = json.encode(drinks), headers = { ['content-type'] = 'application/json; charset=utf8' }}
    end
)

-- PUT endpoint, add data to the database
router:route({ path = '/drinks', method = 'PUT' }, 
    function(req)
        local dspace = box.space.drinks
        -- [1] is https://github.com/multitheftauto/mtasa-blue/issues/1073
        local body = req:json()[1]
        -- box.sequence.drinks_seq:next() is auto-increment
        local drinks = dspace:insert{box.sequence.drinks_seq:next(), body.type, tonumber(body.count)}
        if drinks then
            return {status = 200, body = json.encode(drinks), headers = { ['content-type'] = 'application/json; charset=utf8' }}
        else
            return {status = 200, body = '{"status": "something went wrong"}', headers = { ['content-type'] = 'application/json; charset=utf8' }}
        end
    end
)

-- POST endpoint, edit the data in the database
router:route({ path = '/drinks', method = 'POST' }, 
    function(req)
        local dspace = box.space.drinks
        -- [1] is https://github.com/multitheftauto/mtasa-blue/issues/1073
        local body = req:json()[1]
        -- Well... Complicated thing (not much)
        -- update(key, {{ = is assignment, number of field, value}, ...})
        local drinks = dspace:update(tonumber(body.id), {{'=', 3, tonumber(body.count)}})
        if drinks then
            return {status = 200, body = json.encode(drinks), headers = { ['content-type'] = 'application/json; charset=utf8' }}
        else
            return {status = 200, body = '{"status": "something went wrong"}', headers = { ['content-type'] = 'application/json; charset=utf8' }}
        end
    end
)

httpd:set_router(router)
httpd:start()

 

After that we have to start it.

cd /path/to/saved/script && chmod +x ./server.lua && ./server.lua

 

Part 2.0: MTA resource

The communication side will be simple command drinks with interactive options.

local url = "http://127.0.0.1:3820/drinks"

-- GET endpoint
function showDrinks()
	local sendOptions = {
		queueName = "tarantool.drinks.get",
		method = "GET",
	}
	local successRemote = fetchRemote(url, sendOptions,
		function (responseData, responseInfo)
			local error = responseInfo.statusCode
			if error == 200 then
				print (responseData)
			end
		end

	)
end
-- PUT endpoint
function addDrinks(arg)
	local data = {
		type = tostring(arg[1]),
		count = tonumber(arg[2])
	}
	local sendOptions = {
		queueName = "tarantool.drinks.add",
		postData = toJSON(data),
		method = "PUT",
	}
	local successRemote = fetchRemote(url, sendOptions,
		function (responseData, responseInfo)
			local error = responseInfo.statusCode
			if error == 200 then
				print ("Successfully added")
			end
		end

	)
end

-- POST endpoint
function updateDrinks(arg)
	local data = {
		id = tonumber(arg[1]),
		count = tonumber(arg[2])
	}
	local sendOptions = {
		queueName = "tarantool.drinks.update",
		postData = toJSON(data),
		method = "POST",
	}
	local successRemote = fetchRemote(url, sendOptions,
		function (responseData, responseInfo)
			local error = responseInfo.statusCode
			if error == 200 then
				print ("Successfully updated")
			end
		end

	)
end

-- Command handler
addCommandHandler("drinks", function(ply, cmd, ...)
	local arg = {...}
	local subcmd = arg[1]
	-- subcmd is useless in the table
	table.remove(arg, 1)

	-- case/switch
	if subcmd == 'list' then
		showDrinks()
	end

	if subcmd == 'add' then
		addDrinks(arg)
	end

	if subcmd == 'update' then
		updateDrinks(arg)
	end
end)

 

And… That’s all. Look at the screenshot, it works!

image.png.e6a31f03ba006dcc715b510e21a799d6.png

 

Conclusion: By the idea it must be simple only in resource without HTTP middleware, but at the moment it is impossible due to slow moving MTA progress, so if you like my tutorial and want to push my solution without middleware push 👍 and talk about it there.

A solution without workaround: https://github.com/multitheftauto/mtasa-blue/issues/2208

Edited by Disinterpreter
  • Like 6
Link to post
  • Recently Browsing   0 members

    No registered users viewing this page.

×
×
  • Create New...