Simple PostgreSQL connector module for Garry's Mod using libpq with asynchronousity in mind.
Besides binary module you'll probably need to add async_postgres.lua
to your project.
- Fully asynchronous, yet allows to wait for a query to finish
- Provides full simplified libpq interface
- Simple, robust, and efficient
- Flexible lua module which extends functionality
- Type friendly lua module with documentation
- Go to releases
- Download
async_postgres.lua
andgmsv_async_postgres_xxx.dll
files
Note
If you are unsure which binary to download, you can run this command inside the console of your server
lua_run print("gmsv_async_postgres_" .. (system.IsWindows() and "win" or system.IsOSX() and "osx" or "linux") .. (jit.arch == "x64" and "64" or not system.IsLinux() and "32" or "") .. ".dll")
- Put
gmsv_async_postgres_xxx.dll
inside thegarrysmod/lua/bin/
folder (if the folder does not exist, create it) - Put
async_postgres.lua
insidegarrysmod/lua/autorun/server/
or inside your project folder - Profit 🎉
-
When
queryParams
is used and the parameter isstring
, then the string will be sent as bytes!
You'll need to convert numbers explicitly to thenumber
type, otherwise PostgreSQL will interpret the parameter as a binary integer, and will return an error or unexpected results may happen. -
Result rows are returned as strings, you'll need to convert them to numbers if needed.
-
You'll need to use
Client:unescapeBytea(...)
to convert bytea data to string from the result.
async_postgres.Client
usage example
-- Lua module will require the binary module automatically, and will provide Client and Pool classes
include("async_postgres.lua")
-- See https://www.postgresql.org/docs/current/libpq-connect.html#LIBPQ-CONNSTRING for connection string format
local client = async_postgres.Client("postgresql://postgres:postgres@localhost")
-- Do not forget to connect to the server
client:connect(function(ok, err)
assert(ok, err)
print("Connected to " .. client:host() .. ":" .. client:port())
end)
-- PostgreSQL can only process one query at a time,
-- but async_postgres.Client has an internal queue for queries
-- so you can queue up as many queries as you want
-- and they will be executed one by one when possible
--
-- For example, this query will be executed after the connection is established
client:query("select now()", function(ok, res)
assert(ok, res)
print("Current time is " .. res.rows[1].now)
end)
-- You can also pass parameters to the query without the need of using client:escape(...)
client:queryParams("select $1 as a, $2 as b", { 5, 10 }, function(ok, res)
assert(ok, res)
PrintTable(res.rows) -- will output [1] = { ["a"] = "5", ["b"] = "10" }
end)
client:prepare("test", "select $1 as value", function(ok, err)
assert(ok, err)
end)
client:queryPrepared("test", { "foobar" }, function(ok, res)
assert(ok, res)
print("Value is: " .. res.rows[1].value) -- will output "Value is foobar"
end)
-- You can also wait for all queries to finish
while client:isBusy() do
-- Will wait for the next query/reset to finish
client:wait()
end
-- And :close() will close the connection to the server
client:close() -- passing true will wait for all queries to finish, but we did waiting in the loop above
async_postgres.Pool
usage example
local pool = async_postgres.Pool("postgresql://postgres:postgres@localhost")
-- You can make the same queries as with Client
-- but with Pool you don't need to worry about connection
-- Pool will manage connections for you
pool:query("select now()", function(ok, res)
assert(ok, res)
print("Current time is " .. res.rows[1].now)
end)
-- With Pool you also can create transactions
-- callback will be called in coroutine with ctx
-- which has query methods that don't need callback
-- and will return results directly
pool:transaction(function(ctx)
-- you can also use ctx.client for methods like :db() if you want
local res = ctx:query("select now()")
print("Time in transaction is: " .. res.rows[1].now)
local res = ctx:query("select $1 as value", { "barfoo" })
print("Value in transaction is: " .. res.rows[1].value) -- will output "Value in transaction is barfoo"
-- If error happens in transaction, it will be rolled back
error("welp something went wrong :p")
-- if no error happens, transaction will be committed
end)
-- Or you can use :connect() to create your own transactions or for something else
-- :connect() will acquire the first available connected Client from the Pool
-- and you'll need to call client:release() when you're done
pool:connect(function(client)
client:query("select now()", function(ok, res)
client:release() -- don't forget to release the client!
assert(ok, res)
print("Current time is " .. res.rows[1].now)
end)
end)
async_postgres.VERSION
: string, e.g., "1.0.0"async_postgres.BRANCH
: string, e.g., "main"async_postgres.URL
: stringasync_postgres.PQ_VERSION
: number, e.g., 160004async_postgres.LUA_API_VERSION
: number, e.g., 1async_postgres.CONNECTION_OK
: numberasync_postgres.CONNECTION_BAD
: numberasync_postgres.PQTRANS_IDLE
: numberasync_postgres.PQTRANS_ACTIVE
: numberasync_postgres.PQTRANS_INTRANS
: numberasync_postgres.PQTRANS_INERROR
: numberasync_postgres.PQTRANS_UNKNOWN
: numberasync_postgres.PQERRORS_TERSE
: numberasync_postgres.PQERRORS_DEFAULT
: numberasync_postgres.PQERRORS_VERBOSE
: numberasync_postgres.PQERRORS_SQLSTATE
: numberasync_postgres.PQSHOW_CONTEXT_NEVER
: numberasync_postgres.PQSHOW_CONTEXT_ERRORS
: numberasync_postgres.PQSHOW_CONTEXT_ALWAYS
: number
async_postgres.Client(conninfo)
: Creates a new client instance
Client:connect(callback)
: Connects to the database (or reconnects if was connected)Client:reset(callback)
: Reconnects to the databaseClient:query(query, callback)
: Sends a query to the serverClient:queryParams(query, params, callback)
: Sends a query with parameters to the serverClient:prepare(name, query, callback)
: Creates a prepared statementClient:queryPrepared(name, params, callback)
: Executes a prepared statementClient:describePrepared(name, callback)
: Describes a prepared statementClient:describePortal(name, callback)
: Describes a portalClient:close(wait)
: Closes the connection to the databaseClient:pendingQueries()
: Returns the number of queued queries (excludes currently executing query)Client:db()
: Returns the database nameClient:user()
: Returns the user nameClient:pass()
: Returns the passwordClient:host()
: Returns the host nameClient:hostaddr()
: Returns the server IP addressClient:port()
: Returns the portClient:transactionStatus()
: Returns the transaction status (seePQTRANS
enums)Client:parameterStatus(paramName)
: Looks up a current parameter settingClient:protocolVersion()
: Interrogates the frontend/backend protocol being usedClient:serverVersion()
: Returns the server version as integerClient:errorMessage()
: Returns the last error messageClient:backendPID()
: Returns the backend process IDClient:sslInUse()
: Returns true if SSL is usedClient:sslAttribute(name)
: Returns SSL-related informationClient:encryptPassword(user, password, algorithm)
: Prepares the encrypted form of a passwordClient:escape(str)
: Escapes a string for use within an SQL commandClient:escapeIdentifier(str)
: Escapes a string for use as an SQL identifierClient:escapeBytea(str)
: Escapes binary data for use within an SQL commandClient:unescapeBytea(str)
: Converts an escaped bytea data into binary dataClient:release(suppress)
: Releases the client back to the pool
Client:onNotify(channel, payload, backendPID)
: Called when a NOTIFY message is receivedClient:onNotice(message, errdata)
: Called when the server sends a notice/warning message during a queryClient:onError(message)
: Called whenever an error occurs inside connect/query callbackClient:onEnd()
: Called whenever connection to the server is lost/closed
async_postgres.Pool(conninfo)
: Creates a new pool instance
Pool:connect(callback)
: Acquires a client from the poolPool:query(query, callback)
: Sends a query to the serverPool:queryParams(query, params, callback)
: Sends a query with parameters to the serverPool:prepare(name, query, callback)
: Creates a prepared statementPool:queryPrepared(name, params, callback)
: Executes a prepared statementPool:describePrepared(name, callback)
: Describes a prepared statementPool:describePortal(name, callback)
: Describes a portalPool:transaction(callback)
: Begins a transaction and runs the callback with a transaction context
Pool:onConnect(client)
: Called when a new client connection is establishedPool:onAcquire(client)
: Called when a client is acquiredPool:onError(message, client)
: Called when an error occursPool:onRelease(client)
: Called when a client is released back to the pool
Please check async_postgres.lua
for full interface documentation.
And you also can check libpq documentation for more information on method behavior.
- goobie-mysql for inspiring with many good ideas
- @unknown-gd for being a good friend