diff options
-rw-r--r-- | comments/build.zig | 6 | ||||
-rw-r--r-- | comments/build.zig.zon | 4 | ||||
-rw-r--r-- | comments/src/main.zig | 2 | ||||
-rw-r--r-- | comments/src/pq.zig | 151 | ||||
-rw-r--r-- | zig-comments/.gitignore | 2 | ||||
-rw-r--r-- | zig-comments/.vscode/launch.json | 16 |
6 files changed, 8 insertions, 173 deletions
diff --git a/comments/build.zig b/comments/build.zig index a678527..eb8f8fb 100644 --- a/comments/build.zig +++ b/comments/build.zig @@ -30,9 +30,9 @@ pub fn build(b: *std.Build) void { exe.addModule("zws", zws.module("zigwebserver")); exe.linkLibrary(zws.artifact("zigwebserver")); - exe.linkLibC(); - exe.linkSystemLibrary("libpq"); - exe.addIncludePath(.{ .path = "/usr/include" }); + const pq = b.dependency("pq", opts); + exe.addModule("pq", pq.module("pq")); + exe.linkLibrary(pq.artifact("pq")); const mustache = b.dependency("mustache", opts); exe.addModule("mustache", mustache.module("mustache")); diff --git a/comments/build.zig.zon b/comments/build.zig.zon index 2acd629..47a4d7e 100644 --- a/comments/build.zig.zon +++ b/comments/build.zig.zon @@ -2,6 +2,10 @@ .name = "comments", .version = "0.0.1", .dependencies = .{ + .pq = .{ + .url = "https://code.mfashby.net/pq-zig/snapshot/pq-zig-main.tar.xz", + .hash = "1220002a8cba96583acc07775b060911c9bd74271fb127992ac21854a12274c4ab48", + }, .smtp = .{ .url = "https://code.mfashby.net/smtp-zig/snapshot/smtp-zig-main.tar.xz", .hash = "12205e7087360185eea859ae9265cc4be54c519d5f8d205f66f655d4f8e7e0045dc1", diff --git a/comments/src/main.zig b/comments/src/main.zig index 882202d..8710d95 100644 --- a/comments/src/main.zig +++ b/comments/src/main.zig @@ -1,6 +1,6 @@ const std = @import("std"); const zws = @import("zws"); -const pq = @import("pq.zig"); +const pq = @import("pq"); const mustache = @import("mustache"); const smtp = @import("smtp"); diff --git a/comments/src/pq.zig b/comments/src/pq.zig deleted file mode 100644 index a4fc382..0000000 --- a/comments/src/pq.zig +++ /dev/null @@ -1,151 +0,0 @@ -const std = @import("std"); -const pq = @cImport( - @cInclude("libpq-fe.h"), -); - -pub const PqError = error{PqError}; - -// libpq wrapper -// later, this could be a pure-zig client implementation -pub const Db = struct { - c_conn: *pq.PGconn, - pub fn init(connect_url: [:0]const u8) !Db { - if (pq.PQisthreadsafe() == 0) { - std.log.err("PQisthreadsafe returned 0, can't use libpq in this program", .{}); - return PqError.PqError; - } - var maybe_conn: ?*pq.PGconn = pq.PQconnectdb(connect_url); - if (maybe_conn == null) { - std.log.err("PQconnectdb returned null", .{}); - return PqError.PqError; - } - if (pq.PQstatus(maybe_conn) == pq.CONNECTION_BAD) { - std.log.err("PQstatus returned CONNECTION_BAD: {s}", .{pq.PQerrorMessage(maybe_conn)}); - - return PqError.PqError; - } else if (pq.PQstatus(maybe_conn) != pq.CONNECTION_OK) { - std.log.err("PQstatus returned unknown status {}: {s}", .{ pq.PQstatus(maybe_conn), pq.PQerrorMessage(maybe_conn) }); - return PqError.PqError; - } - return Db{ - .c_conn = maybe_conn.?, - }; - } - - pub fn deinit(self: Db) void { - pq.PQfinish(self.c_conn); - } - - pub fn exec(self: Db, query: [:0]const u8) !void { - var res: ?*pq.PGresult = pq.PQexec(self.c_conn, query); - defer pq.PQclear(res); - var est: pq.ExecStatusType = pq.PQresultStatus(res); - if (est != pq.PGRES_COMMAND_OK) { - std.log.err("PQexec error code {} message {s}", .{ est, pq.PQerrorMessage(self.c_conn) }); - return PqError.PqError; - } - } - - pub fn prepare_statement(self: Db, allocator: std.mem.Allocator, query: [:0]const u8) PqError!Stmt { - return Stmt{ - .db = self, - .aa = std.heap.ArenaAllocator.init(allocator), - .query = query, - }; - } -}; - -pub const Stmt = struct { - const MAX_PARAMS = 128; - db: Db, - query: [:0]const u8, - aa: std.heap.ArenaAllocator, - - n_params: usize = 0, - param_values: [MAX_PARAMS][*c]const u8 = undefined, - did_exec: bool = false, - c_res: ?*pq.PGresult = null, - res_index: c_int = -1, - n_tuples: c_int = -1, - n_fields: c_int = -1, - - pub fn deinit(self: *Stmt) void { - self.aa.deinit(); - if (self.c_res != null) { - pq.PQclear(self.c_res); - } - } - - pub fn step(self: *Stmt) !bool { - if (!self.did_exec) { - self.did_exec = true; - self.c_res = pq.PQexecParams(self.db.c_conn, self.query, @as(c_int, @intCast(self.n_params)), null, &self.param_values, null, null, 0); - const rs = pq.PQresultStatus(self.c_res); - if (rs != pq.PGRES_TUPLES_OK and rs != pq.PGRES_SINGLE_TUPLE and rs != pq.PGRES_COMMAND_OK) { - std.log.err("PQresultStatus {} error: {s}", .{ rs, pq.PQerrorMessage(self.db.c_conn) }); - return PqError.PqError; - } - self.n_tuples = pq.PQntuples(self.c_res); - self.n_fields = pq.PQnfields(self.c_res); - } - self.res_index = self.res_index + 1; - return self.res_index < self.n_tuples; - } - - pub fn read_column(self: Stmt, idx: c_int, comptime T: type) !T { - const value_c: [*c]u8 = pq.PQgetvalue(self.c_res, self.res_index, idx); - const value: []const u8 = std.mem.sliceTo(value_c, 0); - return switch (@typeInfo(T)) { - .Int => std.fmt.parseInt(T, value, 10), - .Pointer => |ptrinfo| blk: { - if (ptrinfo.child != u8) { - @compileError("pointer type []const u8 only is supported in read_column"); - } - if (ptrinfo.size != .Slice) { - @compileError("pointer type []const u8 only is supported in read_column"); - } - break :blk value; - }, - else => @compileError("unhandled type " ++ @tagName(@typeInfo(T)) ++ " in read_column"), - }; - } - - pub fn read_columnN(self: Stmt, name: [:0]const u8, comptime T: type) !T { - const idx = pq.PQfnumber(self.c_res, name.ptr); - if (idx == -1) { - std.log.err("read_columnN ColumnNotFound [{s}]", .{name}); - return error.ColumnNotFound; - } - return read_column(self, idx, T); - } - - pub fn bind(self: *Stmt, idx: usize, t: anytype) !void { - const T = @TypeOf(t); - const value: [:0]const u8 = switch (@typeInfo(T)) { - .Pointer => try std.fmt.allocPrintZ(self.aa.allocator(), "{s}", .{t}), - .Int => try std.fmt.allocPrintZ(self.aa.allocator(), "{d}", .{t}), - else => @compileError("unhandled type " ++ @tagName(@typeInfo(T) ++ " in bind")), - }; - self.param_values[idx] = value.ptr; - self.n_params = @max(self.n_params, idx + 1); - } - - pub fn read_struct(self: Stmt, comptime T: type) !T { - const ti = @typeInfo(T); - var t: T = undefined; - inline for (ti.Struct.fields) |field| { - const name: [:0]const u8 = &addZ(field.name.len, field.name[0..].*); - const val = try self.read_columnN(name, field.type); - @field(t, field.name) = val; - } - return t; - } -}; - -// https://github.com/ziglang/zig/issues/16116 -pub fn addZ(comptime length: usize, value: [length]u8) [length:0]u8 { - var terminated_value: [length:0]u8 = undefined; - terminated_value[length] = 0; - @memcpy(&terminated_value, &value); - return terminated_value; -} diff --git a/zig-comments/.gitignore b/zig-comments/.gitignore deleted file mode 100644 index ee7098f..0000000 --- a/zig-comments/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -zig-out/ -zig-cache/ diff --git a/zig-comments/.vscode/launch.json b/zig-comments/.vscode/launch.json deleted file mode 100644 index 08ebd2a..0000000 --- a/zig-comments/.vscode/launch.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - // Use IntelliSense to learn about possible attributes. - // Hover to view descriptions of existing attributes. - // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 - "version": "0.2.0", - "configurations": [ - { - "type": "lldb", - "request": "launch", - "name": "Debug", - "program": "${workspaceFolder}/zig-out/bin/comments", - "args": [], - "cwd": "${workspaceFolder}" - } - ] -}
\ No newline at end of file |