Move query helpers into common code
This commit is contained in:
parent
e90d9daf77
commit
1ba1b18c39
3 changed files with 72 additions and 50 deletions
16
src/api/services/common.zig
Normal file
16
src/api/services/common.zig
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
const std = @import("std");
|
||||||
|
const util = @import("util");
|
||||||
|
|
||||||
|
pub const Direction = enum {
|
||||||
|
ascending,
|
||||||
|
descending,
|
||||||
|
|
||||||
|
pub const jsonStringify = util.jsonSerializeEnumAsString;
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const PageDirection = enum {
|
||||||
|
forward,
|
||||||
|
backward,
|
||||||
|
|
||||||
|
pub const jsonStringify = util.jsonSerializeEnumAsString;
|
||||||
|
};
|
|
@ -2,6 +2,7 @@ const std = @import("std");
|
||||||
const builtin = @import("builtin");
|
const builtin = @import("builtin");
|
||||||
const util = @import("util");
|
const util = @import("util");
|
||||||
const sql = @import("sql");
|
const sql = @import("sql");
|
||||||
|
const common = @import("./common.zig");
|
||||||
|
|
||||||
const Uuid = util.Uuid;
|
const Uuid = util.Uuid;
|
||||||
const DateTime = util.DateTime;
|
const DateTime = util.DateTime;
|
||||||
|
@ -153,20 +154,8 @@ pub const QueryArgs = struct {
|
||||||
pub const jsonStringify = util.jsonSerializeEnumAsString;
|
pub const jsonStringify = util.jsonSerializeEnumAsString;
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const Direction = enum {
|
pub const Direction = common.Direction;
|
||||||
ascending,
|
pub const PageDirection = common.PageDirection;
|
||||||
descending,
|
|
||||||
|
|
||||||
pub const jsonStringify = util.jsonSerializeEnumAsString;
|
|
||||||
};
|
|
||||||
|
|
||||||
pub const PageDirection = enum {
|
|
||||||
forward,
|
|
||||||
backward,
|
|
||||||
|
|
||||||
pub const jsonStringify = util.jsonSerializeEnumAsString;
|
|
||||||
};
|
|
||||||
|
|
||||||
pub const Prev = std.meta.Child(std.meta.fieldInfo(QueryArgs, .prev).field_type);
|
pub const Prev = std.meta.Child(std.meta.fieldInfo(QueryArgs, .prev).field_type);
|
||||||
pub const OrderVal = std.meta.fieldInfo(Prev, .order_val).field_type;
|
pub const OrderVal = std.meta.fieldInfo(Prev, .order_val).field_type;
|
||||||
|
|
||||||
|
@ -211,30 +200,6 @@ pub const QueryResult = struct {
|
||||||
next_page: QueryArgs,
|
next_page: QueryArgs,
|
||||||
};
|
};
|
||||||
|
|
||||||
const QueryBuilder = struct {
|
|
||||||
array: std.ArrayList(u8),
|
|
||||||
where_clauses_appended: usize = 0,
|
|
||||||
|
|
||||||
pub fn init(alloc: std.mem.Allocator) QueryBuilder {
|
|
||||||
return QueryBuilder{ .array = std.ArrayList(u8).init(alloc) };
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn deinit(self: *const QueryBuilder) void {
|
|
||||||
self.array.deinit();
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn andWhere(self: *QueryBuilder, 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;
|
const max_max_items = 100;
|
||||||
|
|
||||||
pub const QueryError = error{
|
pub const QueryError = error{
|
||||||
|
@ -246,7 +211,7 @@ pub const QueryError = error{
|
||||||
// arguments.
|
// arguments.
|
||||||
// `args.max_items` is only a request, and fewer entries may be returned.
|
// `args.max_items` is only a request, and fewer entries may be returned.
|
||||||
pub fn query(db: anytype, args: QueryArgs, alloc: std.mem.Allocator) !QueryResult {
|
pub fn query(db: anytype, args: QueryArgs, alloc: std.mem.Allocator) !QueryResult {
|
||||||
var builder = QueryBuilder.init(alloc);
|
var builder = sql.QueryBuilder.init(alloc);
|
||||||
defer builder.deinit();
|
defer builder.deinit();
|
||||||
|
|
||||||
try builder.array.appendSlice(
|
try builder.array.appendSlice(
|
||||||
|
@ -266,21 +231,21 @@ pub fn query(db: anytype, args: QueryArgs, alloc: std.mem.Allocator) !QueryResul
|
||||||
if (args.prev) |prev| {
|
if (args.prev) |prev| {
|
||||||
if (prev.order_val != args.order_by) return error.PageArgMismatch;
|
if (prev.order_val != args.order_by) return error.PageArgMismatch;
|
||||||
|
|
||||||
try builder.andWhere(switch (args.order_by) {
|
switch (args.order_by) {
|
||||||
.name => "(name, id)",
|
.name => try builder.andWhere("(name, id)"),
|
||||||
.host => "(host, id)",
|
.host => try builder.andWhere("(host, id)"),
|
||||||
.created_at => "(created_at, id)",
|
.created_at => try builder.andWhere("(created_at, id)"),
|
||||||
});
|
}
|
||||||
_ = try builder.array.appendSlice(switch (args.direction) {
|
switch (args.direction) {
|
||||||
.ascending => switch (args.page_direction) {
|
.ascending => switch (args.page_direction) {
|
||||||
.forward => " > ",
|
.forward => try builder.andWhere(" > "),
|
||||||
.backward => " < ",
|
.backward => try builder.andWhere(" < "),
|
||||||
},
|
},
|
||||||
.descending => switch (args.page_direction) {
|
.descending => switch (args.page_direction) {
|
||||||
.forward => " < ",
|
.forward => try builder.andWhere(" < "),
|
||||||
.backward => " > ",
|
.backward => try builder.andWhere(" > "),
|
||||||
},
|
},
|
||||||
});
|
}
|
||||||
|
|
||||||
_ = try builder.array.appendSlice("($5, $6)");
|
_ = try builder.array.appendSlice("($5, $6)");
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,6 +25,47 @@ pub const Engine = enum {
|
||||||
sqlite,
|
sqlite,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/// Helper for building queries at runtime. All constituent parts of the
|
||||||
|
/// query should be defined at comptime, however the choice of whether
|
||||||
|
/// or not to include them can occur at runtime.
|
||||||
|
pub const QueryBuilder = struct {
|
||||||
|
array: std.ArrayList(u8),
|
||||||
|
where_clauses_appended: usize = 0,
|
||||||
|
|
||||||
|
pub fn init(alloc: std.mem.Allocator) QueryBuilder {
|
||||||
|
return QueryBuilder{ .array = std.ArrayList(u8).init(alloc) };
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deinit(self: *const QueryBuilder) void {
|
||||||
|
self.array.deinit();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Add a chunk of sql to the query without processing
|
||||||
|
pub fn appendSlice(self: *QueryBuilder, comptime sql: []const u8) !void {
|
||||||
|
try self.array.appendSlice(sql);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Add a where clause to the query. Clauses are assumed to be components
|
||||||
|
/// in an overall expression in Conjunctive Normal Form (AND of OR's).
|
||||||
|
/// https://en.wikipedia.org/wiki/Conjunctive_normal_form
|
||||||
|
/// All calls to andWhere must be contiguous, that is, they cannot be
|
||||||
|
/// interspersed with calls to appendSlice
|
||||||
|
pub fn andWhere(self: *QueryBuilder, comptime 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn str(self: *const QueryBuilder) []const u8 {
|
||||||
|
return self.array.items;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// TODO: make this suck less
|
// TODO: make this suck less
|
||||||
pub const Config = union(Engine) {
|
pub const Config = union(Engine) {
|
||||||
postgres: struct {
|
postgres: struct {
|
||||||
|
|
Loading…
Reference in a new issue