SkillAgentSearch skills...

Pg.zig

Native PostgreSQL driver / client for Zig

Install / Use

/learn @karlseguin/Pg.zig
About this skill

Quality Score

0/100

Supported Platforms

Universal

README

Native PostgreSQL driver for Zig

A native PostgresSQL driver / client for Zig. Supports LISTEN.

See or run example/main.zig for a number of examples.

Install

  1. Add pg.zig as a dependency in your build.zig.zon:
zig fetch --save git+https://github.com/karlseguin/pg.zig#master
  1. In your build.zig, add the pg module as a dependency to your program:
const pg_module = b.dependency("pg", .{}).module("pg");

// the executable from your executable/library
const exe = b.addExecutable(.{
  .name = "example",
  ...
  .imports = &.{
    .{ .name = "pg", .module = pg_module },
  },
});

Example

var pool = try pg.Pool.init(allocator, .{
  .size = 5,
  .connect = .{
    .port = 5432,
    .host = "127.0.0.1",
  },
  .auth = .{
    .username = "postgres",
    .database = "postgres",
    .password = "postgres",
    .timeout = 10_000,
  }
});
defer pool.deinit();

var result = try pool.query("select id, name from users where power > $1", .{9000});
defer result.deinit();

while (try result.next()) |row| {
  const id = try row.get(i32, 0);
  // this is only valid until the next call to next(), deinit() or drain()
  const name = try row.get([]u8, 1);
}

Pool

The pool keeps a configured number of database connection open. The acquire() method is used to retrieve a connection from the pool. The pool may start one background thread to attempt to reconnect disconnected connections (or connections which are in an invalid state).

init(allocator: std.mem.allocator, opts: Opts) !*Pool

Initializes a connection pool. Pool options are:

  • size - Number of connections to maintain. Defaults to 10.
  • auth: - See Conn.auth
  • connect: - See the Conn.open.
  • timeout: - The amount of time, in milliseconds, to wait for a connection to be available when acquire() is called.
  • connect_on_init_count: - The # of connections in the pool to eagerly connect during init. Defaults to null which will initiliaze all connections (size). The background reconnector is used to setup the remaining (size - connect_on_init_count) connections. This can be set to 0, to prevent init from failing except in extreme cases (i.e. OOM), but that will hide any configuration/connection issue until the first query is executed.

initUri(allocator: std.mem.Allocator, uri: std.Uri, opts: Opts) !*Pool

Initializes a connection pool using a std.Uri. When using this function, the auth and connect fields of opts should not be set, as these will automatically set based on the provided uri.

const uri = try std.Uri.parse("postgresql://username:password@localhost:5432/database_name");
const pool = try pg.Pool.initUri(allocator, uri, 5, 10_000);
defer pool.deinit();

acquire() !*Conn

Returns a *Conn for the connection pool. Returns an error.Timeout if the connection cannot be acquired (i.e. if the pool remains empty) for the timeout configuration passed to init.

const conn = try pool.acquire();
defer pool.release(conn);
_ = try conn.exec("...", .{...});

release(conn: *Conn) void

Releases the conection back into the pool. Calling pool.release(conn) is the same as calling conn.release().

newListener() !Listener

Returns a new Listener. This function creates a new connection, it does not use/acquire a connection from the pool. It is a convenience function for cases which have already setup a pool (with the connection and authentication configuration) and want to create a listening connection using those settings.

exec / query / queryOpts / row / rowOpts

For single-query operations, the pool offers wrappers around the connection's exec, query, queryOpts, row and rowOpts methods. These are convenience methods.

pool.exec acquires, executes and releases the connection.

pool.query and pool.queryOpts acquire and execute the query. The connection is automatically returned to the pool when result.deinit() is called. Note that this is a special behavior of pool.query. When the result comes explicitly from a conn.query, result.deinit() does not automatically release the connection back into the pool.

pool.row and pool.rowOpts acquire and execute the query. The connection is automatically returned to the pool when row.deinit() is called. Note that this is a special behavior of pool.row. When the result comes explicitly from a conn.row, row.deinit() does not automatically release the connection back into the pool.

Conn

open(allocator: std.mem.Allocator, opts: Opts) !Conn

Opens a connection, or returns an error. Prefer creating connections through the pool. Connection options are:

  • host - Defaults to "127.0.0.1"
  • port - Defaults to 5432
  • write_buffer - Size of the write buffer, used when sending messages to the server. Will temporarily allocate more space as needed. If you're writing large SQL or have large parameters (e.g. long text values), making this larger might improve performance a little. Defaults to 2048, cannot be less than 128.
  • read_buffer - Size of the read buffer, used when reading data from the server. Will temporarily allocate more space as needed. Given most apps are going to be reading rows of data, this can have large impact on performance. Defaults to 4096.
  • result_state_size - Each Result (retrieved via a call to query) carries metadata about the data (e.g. the type of each column). For results with less than or equal to result_state_size columns, a static state container is used. Queries with more columns require a dynamic allocation. Defaults to 32.

deinit(conn: *Conn) void

Closes the connection and releases its resources. This method should not be used when the connection comes from the pool.

auth(opts: Opts) !void

Authentications the request. Prefer creating connections through the pool. Auth options are:

  • username: Defaults to "postgres"
  • password: Defaults to null
  • database: Defaults to null
  • timeout : Defaults to 10_000 (milliseconds)
  • application_name: Defaults to null
  • params: Defaults to null. An std.StringHashMap([]const u8)

release(conn: *Conn) void

Releases the connection back to the pool. The pool might decide to close the connection and open a new one.

exec(sql: []const u8, args: anytype) !?usize

Executes the query with arguments, returns the number of rows affected, or null. Should not be used with a query that returns rows.

query(sql: []const u8, args: anytype) !Result

Executes the query with arguments, returns Result. deinit, and possibly drain, must be called on the returned result.

queryOpts(sql: []const u8, args: anytype, opts: Conn.QueryOpts) !Result

Same as query but takes options:

  • timeout: ?u32 - This is not reliable and should probably not be used. Currently it simply puts a recv socket timeout. On timeout, the connection will likely no longer be valid (which the pool will detect and handle when the connection is released) and the underlying query will likely still execute. Defaults to null.
  • column_names: bool - Whether or not the result.column_names should be populated. When true, this requires memory allocation (duping the column names). Defaults to false unless the column_names build option was set to true.
  • allocator - The allocator to use for any allocations needed when executing the query and reading the results. When null this will default to the connection's allocator. If you were executing a query in a web-request and each web-request had its own arena tied to the lifetime of the request, it might make sense to use that arena. Defaults to null.
  • release_conn: bool - Whether or not to call conn.release() when result.deinit() is called. Useful for writing a function that acquires a connection from a Pool and returns a Result. When query or row are called from a Pool this is forced to true. Otherwise, defaults to false.

row(sql: []const u8, args: anytype) !?QueryRow

Executes the query with arguments, returns a single row. Returns an error if the query returns more than one row. Returns null if the query returns no row. deinit must be called on the returned QueryRow.

rowOpts(sql: []const u8, args: anytype, opts: Conn.QueryOpts) !Result

Same as row but takes the same options as queryOpts.

prepare(sql: []const u8) !Stmt

Creates a Stmt. It is generally better to use query, row or exec.

prepareOpts(sql: []const u8, opts: Conn.QueryOpts) !Stmt

Same as prepare but takes the same options as queryOpts.

begin() !void

Calls _ = try execOpts("begin", .{}, .{})

commit() !void

Calls _ = try execOpts("commit", .{}, .{})

rollback() !void

Calls _ = try execOpts("rollback", .{}, .{})

Result

The conn.query and conn.queryOpts methods return a pg.Result which is used to read rows and values.

Fields

  • number_of_columns: usize - Number of columns in the result.
  • column_names: [][]const u8 - Names of the column, empty unless the query was executed with the column_names = true option or the column_names build option was set to true.

deinit(result: *Result) void

Releases resources associated with the result.

drain(result: *Result) !void

If you do not iterate through the result until next returns null, you must call drain.

Why can't deinit handle this? If deinit also drained, you'd have to handle a possible error in deinit and you can't try in a defer. Thus, this is done to provide better ergonomics for the normal case - the normal case being where next is called until it returns null. In these cases, just defer result.deinit().

next(result: *Result) !?Row

Iterates to the next row of the resu

View on GitHub
GitHub Stars517
CategoryData
Updated3h ago
Forks52

Languages

Zig

Security Score

100/100

Audited on Mar 27, 2026

No findings