Fully working query builder

This commit is contained in:
jaina heartles 2022-08-07 09:58:54 -07:00
parent b6a3b68cff
commit 84e62ffe38

View file

@ -1,5 +1,6 @@
const std = @import("std");
const util = @import("util");
const builtin = @import("builtin");
const String = []const u8;
const comptimePrint = std.fmt.comptimePrint;
@ -8,7 +9,16 @@ fn baseTypeName(comptime T: type) []const u8 {
comptime {
const name = @typeName(T);
const start = for (name) |_, i| {
if (name[name.len - i] == '.') break name.len - i;
if (name[name.len - i] == '.') {
// This function has an off-by-one error in the self hosted compiler (-fno-stage1)
// The following code fixes it as of 2022-08-07
// TODO: Figure out what's going on here
if (builtin.zig_backend == .stage1) {
break name.len - i;
} else {
break name.len - i + 1;
}
}
} else 0;
return name[start..];
@ -22,12 +32,14 @@ fn tableName(comptime T: type) String {
// Represents a table bound to an identifier in a sql query
pub const QueryTable = struct {
Model: type,
as: String,
index: comptime_int,
// Gets a fully qualified field from a literal
pub fn field(comptime self: QueryTable, comptime lit: @Type(.EnumLiteral)) String {
const f = @as(std.meta.FieldEnum(self.Model), lit);
return comptimePrint("{s}.{s}", .{ self.as, @tagName(f) });
comptime {
const f = @as(std.meta.FieldEnum(self.Model), lit);
return comptimePrint("{s}.{s}", .{ self.as(), @tagName(f) });
}
}
pub fn select(comptime self: QueryTable, comptime lit: @Type(.EnumLiteral)) ResultColumn {
@ -40,39 +52,34 @@ pub const QueryTable = struct {
// returns the declaration to put in the FROM clause
fn declarationStr(comptime self: QueryTable) String {
comptime {
return comptimePrint("{s} AS {s}", .{ tableName(self.Model), self.as });
return comptimePrint("{s} AS {s}", .{ tableName(self.Model), self.as() });
}
}
fn as(comptime self: QueryTable) String {
comptime {
return comptimePrint("{s}_{}", .{ tableName(self.Model), self.index });
}
}
};
fn makeQueryTable(comptime Model: type, comptime table_index: usize) QueryTable {
return .{
.Model = Model,
.as = comptimePrint("{s}_{}", .{ tableName(Model), table_index }),
};
return .{ .Model = Model, .index = table_index };
}
pub fn queryTables(comptime models: []const type) *const [models.len]QueryTable {
return map(type, QueryTable, models, makeQueryTable);
}
test "QueryTable.field" {
const tbl = QueryTable{
.Model = struct { id: i32 },
.as = "my_type",
};
try std.testing.expectEqualStrings("my_type.id", tbl.field(.id));
}
test "QueryTable.declarationStr" {
const MyTable = struct { id: i64 };
const tbl = QueryTable{
.Model = MyTable,
.as = "some_table",
.index = 0,
};
try std.testing.expectEqualStrings("my_table AS some_table", tbl.declarationStr());
try std.testing.expectEqualStrings("my_table AS my_table_0", tbl.declarationStr());
try std.testing.expectEqualStrings("my_table_0.id", tbl.field(.id));
}
test "queryTables constructor" {
@ -83,16 +90,16 @@ test "queryTables constructor" {
try std.testing.expectEqual(MyTable, qt[0].Model);
try std.testing.expectEqual(MyOtherTable, qt[1].Model);
try std.testing.expectEqualStrings("my_table_0", qt[0].as);
try std.testing.expectEqualStrings("my_other_table_1", qt[1].as);
try std.testing.expectEqualStrings("my_table_0", qt[0].as());
try std.testing.expectEqualStrings("my_other_table_1", qt[1].as());
}
fn map(comptime T: type, comptime R: type, comptime vals: []const T, comptime func: anytype) *const [vals.len]R {
var result: [vals.len]R = undefined;
if (@typeInfo(@TypeOf(func)).Fn.args.len == 2) {
for (vals) |v, i| result[i] = @as(R, func(v, i));
inline for (vals) |v, i| result[i] = @as(R, func(v, i));
} else {
for (vals) |v, i| result[i] = @as(R, func(v));
inline for (vals) |v, i| result[i] = @as(R, func(v));
}
return &result;
@ -144,6 +151,18 @@ pub const Condition = union(enum) {
}});
}
}
pub fn eql(comptime lhs: String, comptime rhs: String) Condition {
return .{
.eql = .{ .lhs = lhs, .rhs = rhs },
};
}
pub fn all(comptime cs: []const Condition) Condition {
return .{
.all = cs,
};
}
};
test "Condition.str()" {
@ -172,9 +191,9 @@ const ResultColumn = struct {
return self.field;
}
pub fn toStructField(comptime self: ResultColumn) std.builtin.Type.StructField {
pub fn toStructField(comptime self: ResultColumn, comptime index: usize) std.builtin.Type.StructField {
return .{
.name = self.field,
.name = comptimePrint("{}", .{index}),
.field_type = self.@"type",
.default_value = null,
.is_comptime = false,
@ -189,13 +208,23 @@ pub const Query = struct {
fields: []const ResultColumn,
filter: Condition,
pub fn str(comptime self: Query) String {
const table_aliases = map(QueryTable, String, self.tables, QueryTable.declarationStr);
const select_clauses = map(ResultColumn, String, self.fields, ResultColumn.toSelectClause);
return comptimePrint("SELECT {s} FROM {s} WHERE {s}", .{ join(select_clauses, ", "), join(table_aliases, ", "), self.filter.str() });
pub fn from(comptime tables: []const QueryTable) Query {
return .{
.tables = tables,
.fields = &.{},
.filter = .{ .val = "TRUE" }, // TODO
};
}
pub fn rowType(comptime self: Query) type {
pub fn str(comptime self: Query) String {
comptime {
const table_aliases = map(QueryTable, String, self.tables, QueryTable.declarationStr);
const select_clauses = map(ResultColumn, String, self.fields, ResultColumn.toSelectClause);
return comptimePrint("SELECT {s} FROM {s} WHERE {s}", .{ join(select_clauses, ", "), join(table_aliases, ", "), self.filter.str() });
}
}
pub fn rowType(comptime self: *const Query) type {
const struct_fields = map(ResultColumn, std.builtin.Type.StructField, self.fields, ResultColumn.toStructField);
return @Type(.{ .Struct = .{
@ -205,24 +234,47 @@ pub const Query = struct {
.is_tuple = true,
} });
}
pub fn select(comptime self: Query, comptime fields: []const ResultColumn) Query {
return .{
.tables = self.tables,
.fields = fields,
.filter = self.filter,
};
}
pub fn where(comptime self: Query, comptime condition: Condition) Query {
return .{
.tables = self.tables,
.fields = self.fields,
.filter = condition,
};
}
};
test "Query" {
const C = Condition;
const MyTable = struct { id: i64 };
const MyOtherTable = struct {
val: []const u8,
};
const qt = queryTables(&.{ MyTable, MyOtherTable, MyTable });
const q = comptime Query{
.tables = qt,
.fields = &.{ qt[0].select(.id), qt[1].select(.val) },
.filter = Condition{ .eql = .{ .lhs = qt[0].field(.id), .rhs = qt[2].field(.id) } },
};
const t1 = qt[0];
const t2 = qt[2];
const t_other = qt[1];
const q = comptime Query
.from(qt)
.select(&.{ t1.select(.id), t_other.select(.val) })
.where(C.all(&.{
C.eql(t1.field(.id), t2.field(.id)),
C.eql(t1.field(.id), t2.field(.id)),
}));
try std.testing.expectEqualStrings(
"SELECT my_table_0.id, my_other_table_1.val " ++
"FROM my_table AS my_table_0, my_other_table AS my_other_table_1, my_table AS my_table_2 " ++
"WHERE (my_table_0.id = my_table_2.id)",
"WHERE ((my_table_0.id = my_table_2.id) AND (my_table_0.id = my_table_2.id))",
comptime q.str(),
);