Build paginated query
This commit is contained in:
parent
8763132d35
commit
0a42274b27
2 changed files with 175 additions and 1 deletions
|
@ -324,5 +324,10 @@ fn ApiConn(comptime DbConn: type) type {
|
|||
.created_at = note.created_at,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn queryCommunities(self: *Self, args: services.communities.QueryArgs) ![]services.communities.Community {
|
||||
if (!self.isAdmin()) return error.PermissionDenied;
|
||||
return services.communities.query(&self.db, args, self.arena.allocator());
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
|
@ -8,6 +8,7 @@ const DbError = @import("../db.zig").ExecError;
|
|||
const getRandom = @import("../api.zig").getRandom;
|
||||
|
||||
const Uuid = util.Uuid;
|
||||
const DateTime = util.DateTime;
|
||||
|
||||
const CreateError = error{
|
||||
InvalidOrigin,
|
||||
|
@ -32,8 +33,14 @@ pub const Community = struct {
|
|||
name: []const u8,
|
||||
|
||||
scheme: Scheme,
|
||||
created_at: DateTime,
|
||||
};
|
||||
|
||||
fn freeCommunity(alloc: std.mem.Allocator, c: Community) void {
|
||||
alloc.free(c.host);
|
||||
alloc.free(c.name);
|
||||
}
|
||||
|
||||
pub fn create(db: anytype, origin: []const u8, name: ?[]const u8) CreateError!Community {
|
||||
const scheme_len = firstIndexOf(origin, ':') orelse return error.InvalidOrigin;
|
||||
const scheme_str = origin[0..scheme_len];
|
||||
|
@ -66,6 +73,7 @@ pub fn create(db: anytype, origin: []const u8, name: ?[]const u8) CreateError!Co
|
|||
.host = host,
|
||||
.name = name orelse host,
|
||||
.scheme = scheme,
|
||||
.created_at = DateTime.now(),
|
||||
};
|
||||
|
||||
if ((try db.execRow(&.{Uuid}, "SELECT id FROM community WHERE host = ?", .{host}, null)) != null) {
|
||||
|
@ -86,7 +94,7 @@ fn firstIndexOf(str: []const u8, ch: u8) ?usize {
|
|||
}
|
||||
|
||||
pub fn getByHost(db: anytype, host: []const u8, alloc: std.mem.Allocator) !Community {
|
||||
const result = (try db.execRow(&.{ Uuid, ?Uuid, []const u8, []const u8, Scheme }, "SELECT id, owner_id, host, name, scheme FROM community WHERE host = ?", .{host}, alloc)) orelse return error.NotFound;
|
||||
const result = (try db.execRow(&.{ Uuid, ?Uuid, []const u8, []const u8, Scheme, DateTime }, "SELECT id, owner_id, host, name, scheme, created_at FROM community WHERE host = ?", .{host}, alloc)) orelse return error.NotFound;
|
||||
|
||||
return Community{
|
||||
.id = result[0],
|
||||
|
@ -94,9 +102,170 @@ pub fn getByHost(db: anytype, host: []const u8, alloc: std.mem.Allocator) !Commu
|
|||
.host = result[2],
|
||||
.name = result[3],
|
||||
.scheme = result[4],
|
||||
.created_at = result[5],
|
||||
};
|
||||
}
|
||||
|
||||
pub fn transferOwnership(db: anytype, community_id: Uuid, new_owner: Uuid) !void {
|
||||
_ = try db.execRow(&.{i64}, "UPDATE community SET owner_id = ? WHERE id = ?", .{ new_owner, community_id }, null);
|
||||
}
|
||||
|
||||
pub const QueryArgs = struct {
|
||||
pub const OrderBy = enum {
|
||||
name,
|
||||
host,
|
||||
created_at,
|
||||
};
|
||||
|
||||
// Max items to fetch
|
||||
max_items: usize = 20,
|
||||
|
||||
// Selection filters
|
||||
owner_id: ?Uuid = null, // searches for communities owned by this user
|
||||
like: ?[]const u8 = null, // searches for communities with host or name LIKE '%?%'
|
||||
created_before: ?DateTime = null,
|
||||
created_after: ?DateTime = null,
|
||||
|
||||
// Ordering parameter
|
||||
order_by: OrderBy = .created_at,
|
||||
direction: enum {
|
||||
ascending,
|
||||
descending,
|
||||
} = .ascending,
|
||||
|
||||
// Page start parameter
|
||||
// This struct is a reference to the last value scanned
|
||||
// If prev is present, then prev.order_val must have the same tag as order_by
|
||||
// "prev" here refers to it being the previous value returned. It may be that
|
||||
// prev refers to the item directly after the results you are about to recieve,
|
||||
// if you are querying the previous page.
|
||||
prev: ?struct {
|
||||
id: Uuid,
|
||||
order_val: union(OrderBy) {
|
||||
name: []const u8,
|
||||
host: []const u8,
|
||||
created_at: DateTime,
|
||||
},
|
||||
} = null,
|
||||
|
||||
// What direction to scan the page window
|
||||
// If "forward", then "prev" is interpreted as the item directly before the items
|
||||
// to query, in the direction of "direction" above. If "backward", then the opposite
|
||||
page_direction: enum {
|
||||
forward,
|
||||
backward,
|
||||
} = .forward,
|
||||
};
|
||||
|
||||
const Builder = struct {
|
||||
array: std.ArrayList(u8),
|
||||
where_clauses_appended: usize = 0,
|
||||
|
||||
pub fn init(alloc: std.mem.Allocator) Builder {
|
||||
return Builder{ .array = std.ArrayList(u8).init(alloc) };
|
||||
}
|
||||
|
||||
pub fn deinit(self: *const Builder) void {
|
||||
self.array.deinit();
|
||||
}
|
||||
|
||||
pub fn andWhere(self: *Builder, clause: []const u8) !void {
|
||||
if (self.where_clauses_appended == 0) {
|
||||
try self.array.appendSlice("WHERE ");
|
||||
} else {
|
||||
try self.array.appendSlice(" AND ");
|
||||
}
|
||||
|
||||
try self.array.appendSlice(clause);
|
||||
self.where_clauses_appended += 1;
|
||||
}
|
||||
};
|
||||
|
||||
const max_max_items = 100;
|
||||
pub fn query(db: anytype, args: QueryArgs, alloc: std.mem.Allocator) ![]Community {
|
||||
var builder = Builder.init(alloc);
|
||||
defer builder.deinit();
|
||||
|
||||
try builder.array.appendSlice(
|
||||
\\SELECT id, owner_id, host, name, scheme, created_at
|
||||
\\FROM community
|
||||
\\
|
||||
);
|
||||
const max_items = if (args.max_items > max_max_items) max_max_items else args.max_items;
|
||||
|
||||
if (args.owner_id != null) try builder.andWhere("owner_id = $1");
|
||||
if (args.like != null) try builder.andWhere("(host LIKE ('%' + $2 + '%') OR name LIKE ('%' + $2 + '%'))");
|
||||
if (args.created_before != null) try builder.andWhere("created_at < $3");
|
||||
if (args.created_after != null) try builder.andWhere("created_at > $4");
|
||||
if (args.prev) |prev| {
|
||||
if (prev.order_val != args.order_by) return error.PageArgMismatch;
|
||||
|
||||
try builder.andWhere(switch (args.order_by) {
|
||||
.name => "(name, id)",
|
||||
.host => "(host, id)",
|
||||
.created_at => "(created_at, id)",
|
||||
});
|
||||
_ = try builder.array.appendSlice(switch (args.direction) {
|
||||
.ascending => switch (args.page_direction) {
|
||||
.forward => " > ",
|
||||
.backward => " < ",
|
||||
},
|
||||
.descending => switch (args.page_direction) {
|
||||
.forward => " < ",
|
||||
.backward => " > ",
|
||||
},
|
||||
});
|
||||
|
||||
_ = try builder.array.appendSlice("($5, $6)");
|
||||
}
|
||||
_ = try builder.array.appendSlice("\nORDER BY ");
|
||||
_ = try builder.array.appendSlice(@tagName(args.order_by));
|
||||
_ = try builder.array.appendSlice(", id ");
|
||||
_ = try builder.array.appendSlice(switch (args.direction) {
|
||||
.ascending => "ASC",
|
||||
.descending => "DESC",
|
||||
});
|
||||
|
||||
_ = try builder.array.appendSlice("\nLIMIT $7");
|
||||
|
||||
const query_args = .{
|
||||
args.owner_id,
|
||||
args.like,
|
||||
args.created_before,
|
||||
args.created_after,
|
||||
if (args.prev) |prev| prev.order_val else null,
|
||||
if (args.prev) |prev| prev.id else null,
|
||||
max_items,
|
||||
};
|
||||
|
||||
var results = try db.exec(
|
||||
&.{ Uuid, ?Uuid, []const u8, []const u8, Scheme, DateTime },
|
||||
builder.array.items,
|
||||
query_args,
|
||||
);
|
||||
defer results.finish();
|
||||
|
||||
const result_buf = try alloc.alloc(Community, args.max_items);
|
||||
errdefer alloc.free(result_buf);
|
||||
|
||||
var count: usize = 0;
|
||||
errdefer for (result_buf[0..count]) |c| freeCommunity(alloc, c);
|
||||
|
||||
for (result_buf) |*c| {
|
||||
const row = results.row(alloc) orelse break;
|
||||
c.* = .{
|
||||
.id = row[0],
|
||||
.owner_id = row[1],
|
||||
.host = row[2],
|
||||
.name = row[3],
|
||||
.scheme = row[4],
|
||||
.created_at = row[5],
|
||||
};
|
||||
|
||||
count += 1;
|
||||
}
|
||||
|
||||
if (results.err) |err| return err;
|
||||
|
||||
return result_buf[0..count];
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue