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 friendlylua module with documentation
- Go to releases
- Download
async_postgres.luaandgmsv_async_postgres_xxx.dllfiles
Note
If you are unsure which binary to download, you can run this command inside the console of your server
lua_runprint("gmsv_async_postgres_" .. (system.IsWindows() and"win" orsystem.IsOSX() and"osx" or"linux") .. (jit.arch=="x64" and"64" ornotsystem.IsLinux() and"32" or"") ..".dll")- Put
gmsv_async_postgres_xxx.dllinside thegarrysmod/lua/bin/folder (if the folder does not exist, create it) - Put
async_postgres.luainsidegarrysmod/lua/autorun/server/or inside your project folder - Profit 🎉
When
queryParamsis used and the parameter isstring, then the string will be sent as bytes!
You'll need to convert numbers explicitly to thenumbertype, 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 classesinclude("async_postgres.lua")
-- See https://www.postgresql.org/docs/current/libpq-connect.html#LIBPQ-CONNSTRING for connection string formatlocalclient=async_postgres.Client("postgresql://postgres:postgres@localhost")
-- Do not forget to connect to the serverclient: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 establishedclient: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 finishwhileclient:isBusy() do-- Will wait for the next query/reset to finishclient:wait()
end-- And :close() will close the connection to the serverclient:close() -- passing true will wait for all queries to finish, but we did waiting in the loop aboveasync_postgres.Pool usage example
localpool=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 youpool: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 directlypool:transaction(function(ctx)
-- you can also use ctx.client for methods like :db() if you wantlocalres=ctx:query("select now()")
print("Time in transaction is: " ..res.rows[1].now)
localres=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 backerror("welp something went wrong :p")
-- if no error happens, transaction will be committedend)
-- 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 donepool: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 (seePQTRANSenums)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