aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMartin Ashby <martin@ashbysoft.com>2024-07-19 21:59:42 +0100
committerMartin Ashby <martin@ashbysoft.com>2024-07-19 21:59:42 +0100
commit6257db275bd9bb5794effaa9f7c4f5d6995024ce (patch)
tree75ab2b2ffba7e2d446c686ad5f7c2c67cde88c20
parent54b7021b063a81bc1655a6b4519b9a41cd6ed30b (diff)
downloadmfashby.net-6257db275bd9bb5794effaa9f7c4f5d6995024ce.tar.gz
mfashby.net-6257db275bd9bb5794effaa9f7c4f5d6995024ce.tar.bz2
mfashby.net-6257db275bd9bb5794effaa9f7c4f5d6995024ce.tar.xz
mfashby.net-6257db275bd9bb5794effaa9f7c4f5d6995024ce.zip
Convert to using pg.zig instead of wrapping libpq for postgresql access
Pure zig also means cross compilation works again!
-rw-r--r--comments/build.zig6
-rw-r--r--comments/build.zig.zon8
-rw-r--r--comments/src/main.zig136
-rwxr-xr-xdeploy.sh10
4 files changed, 105 insertions, 55 deletions
diff --git a/comments/build.zig b/comments/build.zig
index c978095..958fb6d 100644
--- a/comments/build.zig
+++ b/comments/build.zig
@@ -29,10 +29,8 @@ pub fn build(b: *std.Build) void {
const zws = b.dependency("zigwebserver", opts);
exe.root_module.addImport("zws", zws.module("zigwebserver"));
- const pq = b.dependency("pq", opts);
- exe.root_module.addImport("pq", pq.module("pq"));
- exe.linkLibC();
- exe.linkSystemLibrary("pq");
+ const pg = b.dependency("pg", opts);
+ exe.root_module.addImport("pg", pg.module("pg"));
const mustache = b.dependency("mustache", opts);
exe.root_module.addImport("mustache", mustache.module("mustache"));
diff --git a/comments/build.zig.zon b/comments/build.zig.zon
index 5740fa1..88bc50c 100644
--- a/comments/build.zig.zon
+++ b/comments/build.zig.zon
@@ -1,10 +1,10 @@
.{
.name = "comments",
.version = "0.0.1",
- .dependencies = .{ .pq = .{
- .url = "https://code.mfashby.net/pq-zig/snapshot/pq-zig-main.tar.xz",
- .hash = "1220895045b9a9333a385a0329d9c11604036f28a2150d10c3ed711d4bab21d4a218",
- //.path = "../../pq-zig"
+ .dependencies = .{ .pg = .{
+ //.url = "https://github.com/karlseguin/pg.zig/archive/fe2965b7651462258bcdabbb9a5d3aa09a404746.tar.gz",
+ //.hash = "12208adc9d7072c98cdfe9dba33d72430e95be1763b6bc4b1091148b997a9281741b",
+ .path = "../../pg.zig"
}, .smtp = .{
.url = "https://code.mfashby.net/smtp-zig/snapshot/smtp-zig-main.tar.xz",
.hash = "1220b42673da5f313d4ce816e41de2f17e4b7ebbae1669904b9c17a385d9997e72ed",
diff --git a/comments/src/main.zig b/comments/src/main.zig
index 165dff0..161f250 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");
+const pg = @import("pg");
const mustache = @import("mustache");
const smtp = @import("smtp");
@@ -8,38 +8,50 @@ const Params = zws.Params;
const Err = error{
AccessDenied,
+ AlreadyConnected,
BrokenPipe,
- ColumnNotFound,
+ Closed,
+ ConnectionBusy,
ConnectionResetByPeer,
ConnectionTimedOut,
DeviceBusy,
DiskQuota,
+ EndOfStream,
+ FieldColumnMismatch,
+ FileDescriptorNotASocket,
FileTooBig,
InputOutput,
InvalidArgument,
InvalidCharacter,
+ InvalidDataRow,
InvalidLength,
- InvalidRequestMethod,
+ InvalidProtocolOption,
+ InvalidUUID,
IsDir,
LockViolation,
Malformatted,
- NetNameDeleted,
+ NetworkSubsystemFailed,
+ NoDevice,
NoSpaceLeft,
+ NotAString,
NotOpenForReading,
NotOpenForWriting,
OperationAborted,
OutOfMemory,
- PqError,
+ Overflow,
+ PermissionDenied,
+ PG,
+ SocketNotBound,
SocketNotConnected,
- StreamTooLong,
SystemResources,
+ TimeoutTooBig,
Unexpected,
+ UnexpectedDBMessage,
WouldBlock,
- Overflow,
- EndOfStream,
};
+
const Ctx = struct {
- db: pq.Db,
+ db: *pg.Conn,
pub fn clone(self: @This()) Ctx {
return Ctx{
.db = self.db,
@@ -127,14 +139,58 @@ const router = Rtr{
.notfound = notfound,
};
+const UriParams = struct {
+ connOpts: pg.Conn.Opts,
+ authOpts: pg.Conn.AuthOpts,
+ aa: std.heap.ArenaAllocator,
+ pub fn deinit(self: *UriParams) void {
+ self.aa.deinit();
+ }
+};
+
+fn toParams(uri: std.Uri, parent_a: std.mem.Allocator) !UriParams {
+ var aa = std.heap.ArenaAllocator.init(parent_a);
+ errdefer aa.deinit();
+ const a = aa.allocator();
+ const co: pg.Conn.Opts = .{
+ .host = if (uri.host) |host| try host.toRawMaybeAlloc(a) else "localhost",
+ .port = uri.port orelse 5432,
+ };
+ const ao: pg.Conn.AuthOpts = .{
+ .username = if (uri.user) |user| try user.toRawMaybeAlloc(a) else "postgres",
+ .password = if (uri.password) |password| try password.toRawMaybeAlloc(a) else "",
+ .database = if (uri.path.isEmpty()) "postgres" else std.mem.trimLeft(u8, try uri.path.toRawMaybeAlloc(a), "/"),
+ // TODO startup parameters
+ };
+ return .{
+ .connOpts = co,
+ .authOpts = ao,
+ .aa = aa,
+ };
+}
+
/// Run as a CGI program!
pub fn main() !void {
const allocator = gpa.allocator();
const db_url = std.posix.getenv("DATABASE_URL") orelse "postgresql://comments@localhost/comments";
- var db = try pq.Db.init(db_url);
+ const uri = try std.Uri.parse(db_url);
+ var uriParams = try toParams(uri, allocator);
+ defer uriParams.deinit();
+ var db = try pg.Conn.open(allocator, uriParams.connOpts);
+ defer db.deinit();
+
+ db.auth(uriParams.authOpts) catch |e| switch (e) {
+ error.PG => {
+ std.log.err("PG error authenticating {s}", .{db.err.?.message});
+ return;
+ },
+ else => {
+ std.log.err("Error authenticating! {}", .{e});
+ return;
+ },
+ };
// try db.exec(@embedFile("migrations/0_init.sql"));
// try db.exec(@embedFile("migrations/1_capcha.sql"));
- defer db.deinit();
const req = Request{
.method = std.meta.stringToEnum(std.http.Method, std.posix.getenv("REQUEST_METHOD") orelse "GET") orelse {
return error.InvalidRequestMethod;
@@ -148,7 +204,7 @@ pub fn main() !void {
.transfer_encoding = .none,
.headers = Headers.init(allocator),
};
- const ctx = Ctx{ .db = db };
+ const ctx = Ctx{ .db = &db };
try router.handle(&res, ctx);
}
@@ -185,16 +241,14 @@ fn get_comment(res: *Response, ctx: Ctx, _: Params) Err!void {
ts: []const u8,
};
var comments = std.ArrayList(Comment).init(res.allocator);
- var stmt = try ctx.db.prepare_statement(res.allocator,
- \\ select author,comment,ts from comments where url = $1 order by ts
- );
- defer stmt.deinit();
- try stmt.bind(0, url);
- while (try stmt.step()) {
- const cmt = try stmt.read_struct(Comment);
- try comments.append(cmt);
+ var qr = try ctx.db.queryOpts(
+ \\ select author,comment,ts::text from comments where url = $1 order by ts
+ , .{url}, .{ .column_names = true });
+ defer qr.deinit();
+ var mapper = qr.mapper(Comment, .{ .allocator = res.allocator, .dupe = true });
+ while (try mapper.next()) |nxt| {
+ try comments.append(nxt);
}
-
const rr = @embedFile("templates/comments.html");
const tt = mustache.parseText(res.allocator, rr, .{}, .{ .copy_strings = false }) catch unreachable;
res.transfer_encoding = .chunked;
@@ -233,30 +287,29 @@ fn post_comment(res: *Response, ctx: Ctx, _: Params) Err!void {
// Validate the capcha
{
- var stmt = try ctx.db.prepare_statement(res.allocator, "select answer from capchas where id = $1");
- defer stmt.deinit();
- try stmt.bind(0, form_val.capcha_id);
- if (!try stmt.step()) {
+ var qr = try ctx.db.query("select answer from capchas where id = $1", .{form_val.capcha_id});
+ defer qr.deinit();
+ const row: pg.Row = try qr.next() orelse {
std.log.err("missing capcha_id {s}", .{form_val.capcha_id});
try badrequest(res, ctx);
return;
- }
- const ans = try stmt.read_column(0, []const u8);
+ };
+ const ans = row.get([]const u8, 0);
if (!std.mem.eql(u8, ans, form_val.capcha_answer)) {
std.log.err("bad capcha answer {s} expected {s}", .{ form_val.capcha_answer, ans });
try constresponse(res, @embedFile("templates/capchainvalid.html"), std.http.Status.unauthorized);
return;
}
+ try qr.drain();
}
// Add the comment...
{
- var stmt = try ctx.db.prepare_statement(res.allocator, "insert into comments(url,author,comment) values($1, $2, $3)");
- defer stmt.deinit();
- try stmt.bind(0, form_val.url);
- try stmt.bind(1, form_val.author);
- try stmt.bind(2, form_val.comment);
- _ = try stmt.step();
+ _ = try ctx.db.exec("insert into comments(url,author,comment) values($1, $2, $3)", .{
+ form_val.url,
+ form_val.author,
+ form_val.comment,
+ });
}
// Send me an email
@@ -292,18 +345,19 @@ fn get_form(res: *Response, ctx: Ctx, _: Params) Err!void {
return;
};
- var stmt = try ctx.db.prepare_statement(res.allocator, "select id, question from capchas order by random() limit 1");
- defer stmt.deinit();
- if (!try stmt.step()) {
- std.log.err("no capcha!", .{});
- try badrequest(res, ctx);
- return;
- }
const Capcha = struct {
id: []const u8,
question: []const u8,
};
- const capcha = try stmt.read_struct(Capcha);
+
+ var qr = try ctx.db.queryOpts("select id::text, question from capchas order by random() limit 1", .{}, .{ .column_names = true });
+ defer qr.deinit();
+ var m = qr.mapper(Capcha, .{ .allocator = res.allocator, .dupe = true });
+ const capcha = try m.next() orelse {
+ std.log.err("no capcha!", .{});
+ try badrequest(res, ctx);
+ return;
+ };
const rr = @embedFile("templates/form.html");
const tt = mustache.parseText(res.allocator, rr, .{}, .{ .copy_strings = false }) catch unreachable;
diff --git a/deploy.sh b/deploy.sh
index 91e476f..48f8c79 100755
--- a/deploy.sh
+++ b/deploy.sh
@@ -7,12 +7,10 @@ zig build
# Build comments app
pushd comments
-if [ $(uname -m) != "aarch64" ]
-then
- echo "must build on aarch64 for now"
- exit
-fi
-zig build -Doptimize=ReleaseSafe -Dcpu=baseline
+zig build \
+ -Doptimize=ReleaseSafe \
+ -Dtarget=aarch64-linux-gnu \
+ -Dcpu=baseline
popd
# Copy static site