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 }