sql-zig

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README

postgres.zig (4684B)


      1 const std = @import("std");
      2 const pq = @cImport(
      3     @cInclude("libpq-fe.h"),
      4 );
      5 const Db = @import("main.zig");
      6 const OpenError = Db.OpenError;
      7 const PrepareError = Db.PrepareError;
      8 const StepError = Db.StepError;
      9 const ColumnError = Db.ColumnError;
     10 const log = @import("log.zig").scoped_log_t(.postgres);
     11 
     12 /// Postgres implementation
     13 /// Single persistent connection implementation of postgres via libpq
     14 pub const Postgres = @This();
     15 
     16 allocator: std.mem.Allocator,
     17 conn: *pq.PGconn,
     18 
     19 /// Connect to a postgres database. URL is format accepted by libpq
     20 pub fn open(allocator: std.mem.Allocator, url: [:0]const u8) OpenError!Db {
     21     if (pq.PQisthreadsafe() == 0) {
     22         log.err("Postgres#open: PQisthreadsafe returned 0, can't use libpq in this program", .{});
     23         return OpenError.NotThreadSafe;
     24     }
     25     var maybe_conn: ?*pq.PGconn = pq.PQconnectdb(url);
     26     if (pq.PQstatus(maybe_conn) != pq.CONNECTION_OK) {
     27         log.err("Postgres#open: PQstatus returned error {}: {s}", .{ pq.PQstatus(maybe_conn), pq.PQerrorMessage(maybe_conn) });
     28         return OpenError.Failed;
     29     }
     30 
     31     var pg = try allocator.create(Postgres);
     32     pg.allocator = allocator;
     33     pg.conn = maybe_conn.?;
     34     return Db{ .ptr = pg, .vtable = .{
     35         .prepare = prepare,
     36         .step = step,
     37         .column_i64 = column_i64,
     38         .column_slice_const_u8 = column_slice_const_u8,
     39         .close_stmt = close_stmt,
     40         .close_db = close_db,
     41     } };
     42 }
     43 
     44 fn prepare(db: *anyopaque, query: [:0]const u8) PrepareError!*anyopaque {
     45     var self: *Postgres = @alignCast(@ptrCast(db));
     46     var pg_stmt = try self.allocator.create(PgStmt);
     47     pg_stmt.* = .{
     48         .query = query,
     49         .params = std.ArrayList([*c]const u8).init(self.allocator),
     50     };
     51     return pg_stmt;
     52 }
     53 
     54 const PgStmt = struct {
     55     query: [:0]const u8,
     56     params: std.ArrayList([*c]const u8),
     57     c_res: ?*pq.PGresult = null,
     58     did_exec: bool = false,
     59     n_tuples: ?c_int = null,
     60     n_fields: ?c_int = null,
     61     res_index: c_int = -1,
     62 };
     63 
     64 // TODO 
     65 // fn bind
     66 
     67 fn step(db: *anyopaque, stmt: *anyopaque) StepError!bool {
     68     var self: *Postgres = @alignCast(@ptrCast(db));
     69     var pgstmt: *PgStmt = @alignCast(@ptrCast(stmt));
     70     if (!pgstmt.did_exec) {
     71         const params = try pgstmt.params.toOwnedSlice();
     72         pgstmt.c_res = pq.PQexecParams(self.conn, pgstmt.query, @intCast(params.len), null, params.ptr, null, null, 0);
     73         const rs = pq.PQresultStatus(pgstmt.c_res);
     74         if (rs != pq.PGRES_TUPLES_OK and rs != pq.PGRES_SINGLE_TUPLE and rs != pq.PGRES_COMMAND_OK) {
     75             log.err("PQresultStatus {} error: {s}", .{ rs, pq.PQerrorMessage(self.conn) });
     76             return StepError.Failed;
     77         }
     78         pgstmt.n_tuples = pq.PQntuples(pgstmt.c_res);
     79         pgstmt.n_fields = pq.PQnfields(pgstmt.c_res);
     80         pgstmt.did_exec = true;
     81     }
     82     pgstmt.res_index = pgstmt.res_index + 1;
     83     return pgstmt.res_index < pgstmt.n_tuples.?;
     84 }
     85 
     86 fn column_i64(db: *anyopaque, stmt: *anyopaque, idx: u31) ColumnError!?i64 {
     87     _ = db;
     88     var pgstmt: *PgStmt = @alignCast(@ptrCast(stmt));
     89     if (!pgstmt.did_exec) @panic("did_exec == false you must call exec then step before trying column");
     90     if (pgstmt.res_index == -1) @panic("res_index == -1 you must call exec then step before trying column");
     91     if (pq.PQgetisnull(pgstmt.c_res, pgstmt.res_index, idx) == 1) {
     92         return null;
     93     }
     94     const value_c: [*c]const u8 = pq.PQgetvalue(pgstmt.c_res, pgstmt.res_index, idx);
     95     const slice = std.mem.sliceTo(value_c, 0);
     96     return try std.fmt.parseInt(i64, slice, 10);
     97 }
     98 
     99 fn column_slice_const_u8(db: *anyopaque, stmt: *anyopaque, idx: u31) ColumnError!?[:0]const u8 {
    100     _ = db;
    101     var pgstmt: *PgStmt = @alignCast(@ptrCast(stmt));
    102     if (!pgstmt.did_exec) @panic("did_exec == false you must call exec then step before trying column");
    103     if (pgstmt.res_index == -1) @panic("res_index == -1 you must call exec then step before trying column");
    104     if (pq.PQgetisnull(pgstmt.c_res, pgstmt.res_index, idx) == 1) {
    105         return null;
    106     }
    107     const value_c: ?[*:0]u8 = pq.PQgetvalue(pgstmt.c_res, pgstmt.res_index, idx);
    108     const value_c_nonnull = value_c orelse return null;
    109     return std.mem.sliceTo(value_c_nonnull, 0);
    110 }
    111 
    112 fn close_stmt(db: *anyopaque, stmt: *anyopaque) void {
    113     var self: *Postgres = @alignCast(@ptrCast(db));
    114     var pgstmt: *PgStmt = @alignCast(@ptrCast(stmt));
    115     if (pgstmt.c_res != null) {
    116         pq.PQclear(pgstmt.c_res.?);
    117     }
    118     pgstmt.params.deinit();
    119     self.allocator.destroy(pgstmt);
    120 }
    121 
    122 fn close_db(db: *anyopaque) void {
    123     var self: *Postgres = @alignCast(@ptrCast(db));
    124     pq.PQfinish(self.conn);
    125     self.allocator.destroy(self);
    126 }