Pg.zig
Native PostgreSQL driver / client for Zig
Install / Use
/learn @karlseguin/Pg.zigREADME
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
- Add pg.zig as a dependency in your
build.zig.zon:
zig fetch --save git+https://github.com/karlseguin/pg.zig#master
- In your
build.zig, add thepgmodule 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 to10.auth: - See Conn.authconnect: - See the Conn.open.timeout: - The amount of time, in milliseconds, to wait for a connection to be available whenacquire()is called.connect_on_init_count: - The # of connections in the pool to eagerly connect duringinit. Defaults tonullwhich will initiliaze all connections (size). The background reconnector is used to setup the remaining (size - connect_on_init_count) connections. This can be set to0, to preventinitfrom 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 to5432write_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 to2048, cannot be less than128.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 to4096.result_state_size- EachResult(retrieved via a call toquery) carries metadata about the data (e.g. the type of each column). For results with less than or equal toresult_state_sizecolumns, a staticstatecontainer is used. Queries with more columns require a dynamic allocation. Defaults to32.
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 tonulldatabase: Defaults tonulltimeout: Defaults to10_000(milliseconds)application_name: Defaults tonullparams: Defaults tonull. Anstd.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 tonull.column_names: bool- Whether or not theresult.column_namesshould be populated. When true, this requires memory allocation (duping the column names). Defaults tofalseunless thecolumn_namesbuild option was set to true.allocator- The allocator to use for any allocations needed when executing the query and reading the results. Whennullthis 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 tonull.release_conn: bool- Whether or not to callconn.release()whenresult.deinit()is called. Useful for writing a function that acquires a connection from aPooland returns aResult. Whenqueryorroware called from aPoolthis is forced totrue. Otherwise, defaults tofalse.
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 thecolumn_names = trueoption or thecolumn_namesbuild 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
