[FREE] DatabaseManager

Hey everyone!

It’s been a while since I released anything of value since forever. So today I release my side project that allows you to quickly execute simple SQL queries to your database without anything more to think about.

I’ll start with I plan on improving and further extending the functionality and optimization with this script, if you encounter any problems at any point I’ll always try to help as fast and best as I can! :slight_smile:

Here are some before and afters from my friend after he refactored his code the first time.

---before
RegisterNetEvent("esx:playerLoaded", function(player, xPlayer, isNew)
    local identifier = xPlayer.getIdentifier()
    MySQL.query("SELECT `level`, `xp` FROM `postop_job` WHERE `identifier` = ?", {
        identifier
    }, function(response)
        if response then
            if response[1] then
                Players[player] = { level = response[1].level, xp = response[1].xp }
            else
                MySQL.insert.await("INSERT INTO `postop_job` (identifier) VALUES (?)", {
                    identifier
                })
                Players[player] = { level = 1, xp = 0 }
            end
        end
    end)
end)
---after
-- prepared objects are reusable
local PJDB = exports.DatabaseManager:GetDatabaseTableManager("postop_job")

local PJDB_DATA_SELECT = PJDB.Prepare.Select({
    "identifier"
})

local PJDB_DATA_INSERT = PJDB.Prepare.Insert({
    "identifier"
})

RegisterNetEvent("esx:playerLoaded", function(player, xPlayer, isNew)
    local identifier = xPlayer.getIdentifier()
    local data = PJDB_DATA_SELECT.execute(identifier)
    if data then
        Players[player] = { level = data.level, xp = data.xp }
    else
        PJDB_DATA_INSERT.execute(identifier)
        Players[player] = { level = 1, xp = 0 }
    end
end)

In DatabaseManager I also included multiple other functions and methods that allow you to further customize the way you type queries using prepared functions for a database table.

local DM = exports.DatabaseManager:GetDatabaseTableManager("table")
---DM.Create(args: {string: column_name, string: column_types}[])

-- Overloaded functions with the same name but different parameters
---DM.Prepare.Select(conditions: table|nil,  cb: function|nil, individual: boolean|nil, query: string|nil)
---DM.Prepare.Select(fields: string[],  cb: function|nil, individual: boolean|nil, query: string|nil)
---DM.Prepare.Select(cb: function)
---DM.Prepare.Update(updates: string[], condition: table, cb: function|nil)
---DM.Prepare.Update(updates_fields: string[], condition_fields: string[], cb: function|nil)
-- Every Prepare object returns a table with two functions, `.update(params)` and `.execute(...args)`
-- `.update` simply replaces arguments set at the creation of given prepared object
-- `.execute` executes the query with provided parameters according to desired statement

-- Examples with code could be such as:
---The only part that requires an SQL, of course if you don't know if a table is created already
DM.Create({
    {"id", "INT(11) UNSIGNED PRIMARY KEY AUTO_INCREMENT"},
    {"name", "VARCHAR(255) NOT NULL UNIQUE"},
    {"price", "INT(11) UNSIGNED NULL DEFAULT 0"}
})

local select_row = DM.Prepare.Select({"name"})
print(select_row.execute("some_name"))
local select_row2 = DM.Prepare.Select({
    name = "some_name"
})
print(select_row2.execute())

---update name field based on a column having its value as "some_name"
local update_row = DM.Prepare.Update({"name"}, {
    name = "some_name"
})
update_row.execute("changed_name")
---update name(param 1) based on it's last name(param 2)
local update_row2 = DM.Prepare.Update({"name"}, {"name"})
update_row2.execute({"changed_name"}, {"some_name"})

In the code above I showcased overloaded methods with the Prepare statement. There are other prepared statements such as Delete, DeleteRows, Insert, InsertRows. There are examples on github in examples.lua file, also the entire server.lua and database_utils.lua are documented using LDoc so in any case you can just read which function accepts which parameter types.

As a last sentence I’ll leave a note that this is mainly intended for tables that are to be used in given script, but you also have an option to completely skip the single table access and use exports.DatabaseManager:GetDatabaseManager() and then simply provide table name as a first param to every function, but I leave the rest for you. :smiley:

(github) Fivem-Script-Lab/DatabaseManager

Not required but I include this little list

Code is accessible Yes
Subscription-based No
Lines (approximately) 1100
Requirements oxmysql
Support Always
6 Likes

DO you have some time to chat I want to implement this in my server but I am very new to all this

Hey, Yeah! No problem. Just write any time to my PM and if I have some time we’ll go from there.

Will it work for qbcore as well?

I mean with this code not sure why you’re adding someone to the DB table on player load to store levels lol, if they aren’t in there they don’t have a level so just level one and then just insert when they get XP.

This SQL query assuming you name your identifier a primary key, which you should because one identifier shouldn’t be in this table more than once. will insert the identifier level and xp and if identifier exists it will just update the level and xp.

INSERT INTO `postop_job` (`identifier`, `level`, `xp`) VALUES (:identifier, :level, :xp) ON DUPLICATE KEY UPDATE level = :level, xp = :xp

Or this is literally the same thing as your code, I don’t get how this is simpler or provides any aditional functionality that doesn’t already exist with oxmysql, the number one most used resource on FiveM. This is like an ORM but it doesn’t provide any of the abstraction that ORMs do.

RegisterNetEvent("esx:playerLoaded", function(player, xPlayer, isNew)
    local identifier = xPlayer.getIdentifier()
    local data = MySQL.single.await('SELECT level, xp FROM postop_job WHERE identifier = ?', { identifier })
    if data then
        Players[player] = { level = data.level, xp = data.xp }
    else
        MySQL.insert('INSERT INTO postop_job (identifier) VALUES (?)', { identifier })
        Players[player] = { level = 1, xp = 0 }
    end
end)

As long as you use oxmysql, or mysql-async which has methods that are used by DatabaseManager, it will work normally. This script simply acts as a mysql query wrapper and apart from any compatible MySQL script it will work as intended. :smile:

Well, you missed the point friend, but I give you that the code example is kinda lame, which is why my friend already removed it.

The point of this script is simply to add wrapper functions to query creation. As of now, apart from creating DB Table you don’t need any SQL, that means if you at any point need to edit a query you can simply add another column or condition and add argument to execute.

I’m not getting defensive, simply trying to explain. But if you have any kind of improvements idea feel free to share :smile:

New Update

  • Improved performance on static SELECT queries by up to 50%
  • Improved slightly performance on dynamic SELECT queries
  • Improved overall performance on all queries, reducing much overhead
  • Added new examples in examples.lua

This update was meant to be more of to be continued since I have many plans on how to expand this script. For now I have planned, lazy loading, query to object creation(ORM), JOIN queries, no-SQL database table creation and many more features. If you have any ideas on what I could add, feel free to write :slight_smile:

I’ll also add another note, that this script is meant to streamline to process of managing SQL in your projects, not replace it entirely. It’s simply easier to understand and modify prepared statements later on than rewrite SQL queries completely.

New Update


Version 0.0.2

  • Fixed each table manager export call appending new overloaded methods to overloaded object
  • Added Version Check
  • Added new Overloaded method → DM.Prepare.Update(array, array, boolean) for no callbacks or return values(useful for end of script saving, without pcall)
  • Improved Overload type checking(new type → array, negation type → not:type)
  • Minor Improvements

Download Link in the post(github).

Version 0.0.3


  • Added LIKE operator support for SELECT queries
  • Uncommented methods from DM.SelectRows
  • Minor tweaks

New like Operator:

local DM = exports.GetDatabaseTableManager('users')

local select_users1 = DM.Prepare.Select({'like:identifier'})
local select_users2 = DM.Prepare.Select({'identifier'})

local some_prefix = "char1:"

local users1 = select_users1(some_prefix) -- selects all users using "identifier LIKE ?"
local users2 = select_users2("like:" .. some_prefix) -- both methods are equivalent

0.0.5 (unstable)

  • Added first ORM implementation(examples to be added)
  • Added minor logging
  • Improved script flow