mirror of
https://github.com/jetzig-framework/jetquery.git
synced 2026-09-01 23:33:21 -06:00
Fix DateTime coercion and optional DateTime detection
Update README with an example and feature list
This commit is contained in:
parent
5394d7cf5d
commit
147e20907e
62
README.md
62
README.md
@ -4,6 +4,68 @@ Database query library for [Jetzig](https://github.com/jetzig-framework/jetzig),
|
||||
|
||||
Documentation: [https://www.jetzig.dev/documentation/sections/database/introduction](https://www.jetzig.dev/documentation/sections/database/introduction)
|
||||
|
||||
## Features
|
||||
|
||||
* Comptime SQL generation
|
||||
* PostgreSQL adapter ([pg.zig](https://github.com/karlseguin/pg.zig))
|
||||
* Powerful `WHERE` clause syntax
|
||||
* Object Relational Mapper (ORM)
|
||||
* Migrations
|
||||
* Relations/Associations
|
||||
|
||||
Use [standalone](https://www.jetzig.dev/documentation/sections/database/standalone_usage) or with [Jetzig](https://www.jetzig.dev/).
|
||||
|
||||
```zig
|
||||
const Schema = struct {
|
||||
pub const Cat = Model(
|
||||
@This(),
|
||||
"cats",
|
||||
struct {
|
||||
id: i32,
|
||||
name: []const u8,
|
||||
age: i32,
|
||||
favorite_sport: []const u8,
|
||||
status: []const u8,
|
||||
},
|
||||
.{ .relations = .{ .homes = hasMany(.Home, .{}) } },
|
||||
);
|
||||
|
||||
pub const Home = Model(@This(), "homes", struct { id: i32, cat_id: i32, zip_code: []const u8 }, .{});
|
||||
};
|
||||
|
||||
const query = Query(.postgresql, Schema, .Cat)
|
||||
.join(.inner, .homes)
|
||||
.where(.{
|
||||
.{ .name = "Hercules" }, .OR, .{ .name = "Heracles" },
|
||||
.{ .{ .age, .gt, 4 }, .{ .age, .lt, 10 } },
|
||||
.{ .favorite_sport, .like, "%ball" },
|
||||
.{ .favorite_sport, .not_eql, "basketball" },
|
||||
.{ "my_sql_function(age)", .eql, 100 },
|
||||
.{ .NOT, .{ .{ .age = 1 }, .OR, .{ .age = 2 } } },
|
||||
.{ "age / paws = ? or age * paws < ?", .{ 2, 10 } },
|
||||
.{ .{ .status = null }, .OR, .{ .status = [_][]const u8{ "sleeping", "eating" } } },
|
||||
.{ .homes = .{ .zip_code = "10304" } },
|
||||
});
|
||||
|
||||
var repo = try Repo(.postgresql, Schema).init(std.testing.allocator, .{
|
||||
.adapter = .{
|
||||
.database = "example_database",
|
||||
.hostname = "127.0.0.1",
|
||||
.port = 5432,
|
||||
.username = "postgres",
|
||||
.password = "password",
|
||||
},
|
||||
});
|
||||
|
||||
for (try repo.all(query)) |cat| {
|
||||
std.debug.print("{s} lives in these ZIP codes:\n", .{cat.name});
|
||||
|
||||
for (cat.homes) |home| {
|
||||
std.debug.print("{s}\n", .{home.zip_code});
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Testing
|
||||
|
||||
Use the provided _Docker Compose_ configuration to launch a local test database:
|
||||
|
||||
@ -412,7 +412,7 @@ fn Statement(
|
||||
context,
|
||||
self.field_infos.len,
|
||||
);
|
||||
const clause_values = tree.values(args);
|
||||
const clause_values = tree.values(Adapter, args);
|
||||
|
||||
const arg_values = clause_values.values;
|
||||
const arg_errors = clause_values.errors;
|
||||
@ -620,33 +620,33 @@ fn Statement(
|
||||
|
||||
pub fn update(self: Self, args: anytype) Statement(Adapter, .update, Schema, Model, .{
|
||||
.field_infos = &(jetquery.fields.fieldInfos(Adapter, Model, &.{}, @TypeOf(args), .update) ++
|
||||
timestampsFields(Model, .update)),
|
||||
timestampsFields(Adapter, Model, .update)),
|
||||
}) {
|
||||
const S = Statement(Adapter, .update, Schema, Model, .{
|
||||
.field_infos = &(jetquery.fields.fieldInfos(Adapter, Model, &.{}, @TypeOf(args), .update) ++
|
||||
timestampsFields(Model, .update)),
|
||||
timestampsFields(Adapter, Model, .update)),
|
||||
});
|
||||
return self.extend(S, args, .update);
|
||||
}
|
||||
|
||||
pub fn updateAll(self: Self, args: anytype) Statement(Adapter, .update_all, Schema, Model, .{
|
||||
.field_infos = &(jetquery.fields.fieldInfos(Adapter, Model, &.{}, @TypeOf(args), .update) ++
|
||||
timestampsFields(Model, .update)),
|
||||
timestampsFields(Adapter, Model, .update)),
|
||||
}) {
|
||||
const S = Statement(Adapter, .update_all, Schema, Model, .{
|
||||
.field_infos = &(jetquery.fields.fieldInfos(Adapter, Model, &.{}, @TypeOf(args), .update) ++
|
||||
timestampsFields(Model, .update)),
|
||||
timestampsFields(Adapter, Model, .update)),
|
||||
});
|
||||
return self.extend(S, args, .update);
|
||||
}
|
||||
|
||||
pub fn insert(self: Self, args: anytype) Statement(Adapter, .insert, Schema, Model, .{
|
||||
.field_infos = &(jetquery.fields.fieldInfos(Adapter, Model, &.{}, @TypeOf(args), .insert) ++
|
||||
timestampsFields(Model, .insert)),
|
||||
timestampsFields(Adapter, Model, .insert)),
|
||||
}) {
|
||||
const S = Statement(Adapter, .insert, Schema, Model, .{
|
||||
.field_infos = &(jetquery.fields.fieldInfos(Adapter, Model, &.{}, @TypeOf(args), .insert) ++
|
||||
timestampsFields(Model, .insert)),
|
||||
timestampsFields(Adapter, Model, .insert)),
|
||||
});
|
||||
return self.extend(S, args, .insert);
|
||||
}
|
||||
@ -1309,6 +1309,7 @@ fn Statement(
|
||||
}
|
||||
|
||||
fn timestampsFields(
|
||||
Adapter: type,
|
||||
Model: type,
|
||||
comptime field_context: jetquery.fields.FieldContext,
|
||||
) [timestampsSize(Model, field_context)]jetquery.fields.FieldInfo {
|
||||
@ -1322,7 +1323,7 @@ fn timestampsFields(
|
||||
field_context,
|
||||
);
|
||||
const created_at = jetquery.fields.fieldInfo(
|
||||
jetquery.fields.structField(jetquery.default_column_names.created_at, i64),
|
||||
jetquery.fields.structField(jetquery.default_column_names.created_at, Adapter.DateTimePrimitive),
|
||||
Model,
|
||||
jetquery.default_column_names.created_at,
|
||||
field_context,
|
||||
|
||||
@ -1454,6 +1454,51 @@ test "alterTable" {
|
||||
try std.testing.expect(dogs2.len == 0); // Empty table but valid select columns.
|
||||
}
|
||||
|
||||
test "optional DateTime" {
|
||||
try resetDatabase();
|
||||
|
||||
const Schema = struct {
|
||||
pub const Thing = jetquery.Model(
|
||||
@This(),
|
||||
"things",
|
||||
struct {
|
||||
a: ?jetquery.DateTime,
|
||||
b: ?jetquery.DateTime,
|
||||
},
|
||||
.{},
|
||||
);
|
||||
};
|
||||
var repo = try Repo(.postgresql, Schema).init(std.testing.allocator, .{
|
||||
.adapter = .{
|
||||
.database = "repo_test",
|
||||
.username = "postgres",
|
||||
.hostname = "127.0.0.1",
|
||||
.password = "password",
|
||||
.port = 5432,
|
||||
},
|
||||
});
|
||||
defer repo.deinit();
|
||||
|
||||
try repo.createTable("things", &.{
|
||||
jetquery.schema.table.column("a", .datetime, .{ .optional = true }),
|
||||
jetquery.schema.table.column("b", .datetime, .{ .optional = true }),
|
||||
}, .{});
|
||||
|
||||
const now = jetquery.DateTime.now();
|
||||
|
||||
try repo.insert(.Thing, .{
|
||||
.a = now,
|
||||
.b = null,
|
||||
});
|
||||
|
||||
if (try repo.Query(.Thing).findBy(.{ .a = now }).execute(&repo)) |thing| {
|
||||
defer repo.free(thing);
|
||||
try std.testing.expect(thing.a != null);
|
||||
try std.testing.expect(thing.a.?.eql(now));
|
||||
try std.testing.expect(thing.b == null);
|
||||
} else try std.testing.expect(false);
|
||||
}
|
||||
|
||||
fn resetDatabase() !void {
|
||||
var repo = try Repo(.postgresql, void).init(
|
||||
std.testing.allocator,
|
||||
|
||||
@ -13,6 +13,7 @@ options: Options,
|
||||
connected: bool,
|
||||
lazy_connect: bool = false,
|
||||
|
||||
pub const DateTimePrimitive = i64;
|
||||
pub const Count = i64;
|
||||
pub const Average = i64;
|
||||
pub const Sum = i64;
|
||||
@ -128,8 +129,15 @@ fn resolvedValue(
|
||||
[]const u8,
|
||||
?[]const u8,
|
||||
=> |T| try maybeDupe(allocator, T, row.get(T, column_info.index)),
|
||||
jetquery.jetcommon.types.DateTime => |T| try T.fromUnix(
|
||||
row.get(i64, column_info.index),
|
||||
?jetquery.DateTime => if (row.get(?DateTimePrimitive, column_info.index)) |timestamp|
|
||||
try jetquery.DateTime.fromUnix(
|
||||
timestamp,
|
||||
.microseconds,
|
||||
)
|
||||
else
|
||||
null,
|
||||
jetquery.DateTime => |T| try T.fromUnix(
|
||||
row.get(DateTimePrimitive, column_info.index),
|
||||
.microseconds,
|
||||
),
|
||||
else => |T| @compileError("Unsupported type: " ++ @typeName(T)),
|
||||
@ -732,7 +740,7 @@ fn bindCoerce(value: anytype) BindCoerce(@TypeOf(value)) {
|
||||
|
||||
fn BindCoerce(T: type) type {
|
||||
return switch (T) {
|
||||
jetquery.DateTime => i64,
|
||||
jetquery.DateTime => DateTimePrimitive,
|
||||
else => T,
|
||||
};
|
||||
}
|
||||
|
||||
@ -5,10 +5,11 @@ const jetcommon = @import("jetcommon");
|
||||
const fields = @import("fields.zig");
|
||||
|
||||
pub fn coerce(
|
||||
Adapter: type,
|
||||
Table: type,
|
||||
field_info: fields.FieldInfo,
|
||||
value: anytype,
|
||||
) CoercedValue(fields.ColumnType(Table, field_info), @TypeOf(value)) {
|
||||
) CoercedValue(Adapter, fields.ColumnType(Adapter, Table, field_info), @TypeOf(value)) {
|
||||
switch (field_info.context) {
|
||||
.limit, .offset => return switch (@typeInfo(@TypeOf(value))) {
|
||||
.int, .comptime_int => .{ .value = value },
|
||||
@ -17,28 +18,29 @@ pub fn coerce(
|
||||
else => {},
|
||||
}
|
||||
|
||||
const T = fields.ColumnType(Table, field_info);
|
||||
const T = fields.ColumnType(Adapter, Table, field_info);
|
||||
|
||||
if (T == jetcommon.types.DateTime) return value.microseconds();
|
||||
if (@TypeOf(value) == jetcommon.types.DateTime) return .{ .value = value.microseconds() };
|
||||
if (@TypeOf(value) == ?jetcommon.types.DateTime) return if (value) .{ .value = value.microseconds() } else null;
|
||||
|
||||
return switch (@typeInfo(@TypeOf(value))) {
|
||||
.null => .{ .value = null },
|
||||
.int, .comptime_int => switch (@typeInfo(T)) {
|
||||
.int => .{ .value = @intCast(value) },
|
||||
.bool => .{ .value = value == 1 },
|
||||
else => coerceDelegate(T, value),
|
||||
else => coerceDelegate(Adapter, T, value),
|
||||
},
|
||||
.float, .comptime_float => switch (@typeInfo(T)) {
|
||||
.float => .{ .value = @floatCast(value) },
|
||||
.bool => .{ .value = value == 1.0 },
|
||||
else => coerceDelegate(T, value),
|
||||
else => coerceDelegate(Adapter, T, value),
|
||||
},
|
||||
.bool => switch (@typeInfo(T)) {
|
||||
.bool => .{ .value = value },
|
||||
else => if (comptime canCoerceDelegate(@TypeOf(value)))
|
||||
coerceDelegate(T, value.*)
|
||||
coerceDelegate(Adapter, T, value.*)
|
||||
else
|
||||
coerceBool(T, value),
|
||||
coerceBool(Adapter, T, value),
|
||||
},
|
||||
.pointer => |info| switch (@typeInfo(T)) {
|
||||
.int => switch (@typeInfo(info.child)) {
|
||||
@ -47,15 +49,15 @@ pub fn coerce(
|
||||
// type but we may need a better way to identify strings if another database
|
||||
// adapter does support u8.
|
||||
.Slice => if (@TypeOf(value) == []const u8)
|
||||
coerceInt(T, value)
|
||||
coerceInt(Adapter, T, value)
|
||||
else
|
||||
.{ .value = value },
|
||||
else => .{ .value = value },
|
||||
},
|
||||
else => if (comptime canCoerceDelegate(info.child))
|
||||
coerceDelegate(T, value.*)
|
||||
coerceDelegate(Adapter, T, value.*)
|
||||
else
|
||||
coerceInt(T, value),
|
||||
coerceInt(Adapter, T, value),
|
||||
},
|
||||
.float => switch (@typeInfo(info.child)) {
|
||||
.float => switch (info.size) {
|
||||
@ -63,9 +65,9 @@ pub fn coerce(
|
||||
else => .{ .value = value },
|
||||
},
|
||||
else => if (comptime canCoerceDelegate(info.child))
|
||||
coerceDelegate(T, value.*)
|
||||
coerceDelegate(Adapter, T, value.*)
|
||||
else
|
||||
coerceFloat(T, value),
|
||||
coerceFloat(Adapter, T, value),
|
||||
},
|
||||
.bool => switch (@typeInfo(info.child)) {
|
||||
.bool => switch (info.size) {
|
||||
@ -75,9 +77,9 @@ pub fn coerce(
|
||||
.int, .comptime_int => .{ .value = value.* == 1 },
|
||||
.float, .comptime_float => .{ .value = value.* == 1.0 },
|
||||
else => if (comptime canCoerceDelegate(info.child))
|
||||
coerceDelegate(T, value.*)
|
||||
coerceDelegate(Adapter, T, value.*)
|
||||
else
|
||||
coerceBool(T, value),
|
||||
coerceBool(Adapter, T, value),
|
||||
},
|
||||
.pointer => if (comptime canCoerceDelegate(info.child))
|
||||
coerceDelegate(T, value.*)
|
||||
@ -90,7 +92,7 @@ pub fn coerce(
|
||||
@typeName(T) ++ "` and `" ++ @typeName(info.child) ++ "`"),
|
||||
},
|
||||
.array => .{ .value = &value },
|
||||
else => coerceDelegate(T, value),
|
||||
else => coerceDelegate(Adapter, T, value),
|
||||
};
|
||||
}
|
||||
|
||||
@ -100,7 +102,11 @@ pub fn coerce(
|
||||
// JetQuery, otherwise a typical Zig compile error will occur. This feature is used by
|
||||
// Zmpl for converting Zmpl Values, allowing e.g. request params in Jetzig to be used as
|
||||
// JetQuery whereclause/etc. params.
|
||||
pub fn coerceDelegate(Target: type, value: anytype) CoercedValue(Target, @TypeOf(value)) {
|
||||
pub fn coerceDelegate(
|
||||
Adapter: type,
|
||||
Target: type,
|
||||
value: anytype,
|
||||
) CoercedValue(Adapter, Target, @TypeOf(value)) {
|
||||
const Source = @TypeOf(value);
|
||||
if (comptime canCoerceDelegate(Source)) {
|
||||
const coerced = value.toJetQuery(Target) catch |err| {
|
||||
@ -120,7 +126,7 @@ pub fn canCoerceDelegate(T: type) bool {
|
||||
};
|
||||
}
|
||||
|
||||
pub fn CoercedValue(Target: type, Source: type) type {
|
||||
pub fn CoercedValue(Adapter: type, Target: type, Source: type) type {
|
||||
const T = switch (@typeInfo(Source)) {
|
||||
.null => @TypeOf(null),
|
||||
.pointer => |info| if (info.child == Target and info.size == .Slice)
|
||||
@ -128,7 +134,13 @@ pub fn CoercedValue(Target: type, Source: type) type {
|
||||
else
|
||||
Target,
|
||||
.array => |info| if (info.child == Target) []const Target else Target,
|
||||
else => Target,
|
||||
// TODO
|
||||
else => if (Source == jetcommon.DateTime)
|
||||
Adapter.DateTimePrimitive
|
||||
else if (Source == ?jetcommon.DateTime)
|
||||
?Adapter.DateTimePrimitive
|
||||
else
|
||||
Target,
|
||||
};
|
||||
|
||||
return struct {
|
||||
@ -137,7 +149,7 @@ pub fn CoercedValue(Target: type, Source: type) type {
|
||||
};
|
||||
}
|
||||
|
||||
fn coerceInt(T: type, value: []const u8) CoercedValue(T, @TypeOf(value)) {
|
||||
fn coerceInt(Adapter: type, T: type, value: []const u8) CoercedValue(Adapter, T, @TypeOf(value)) {
|
||||
const coerced = std.fmt.parseInt(T, value, 10) catch |err| {
|
||||
return .{
|
||||
.err = switch (err) {
|
||||
@ -148,7 +160,7 @@ fn coerceInt(T: type, value: []const u8) CoercedValue(T, @TypeOf(value)) {
|
||||
return .{ .value = coerced };
|
||||
}
|
||||
|
||||
fn coerceFloat(T: type, value: []const u8) CoercedValue(T, @TypeOf(value)) {
|
||||
fn coerceFloat(Adapter: type, T: type, value: []const u8) CoercedValue(Adapter, T, @TypeOf(value)) {
|
||||
const coerced = std.fmt.parseFloat(T, value) catch |err| {
|
||||
return .{
|
||||
.err = switch (err) {
|
||||
@ -159,7 +171,7 @@ fn coerceFloat(T: type, value: []const u8) CoercedValue(T, @TypeOf(value)) {
|
||||
return .{ .value = coerced };
|
||||
}
|
||||
|
||||
fn coerceBool(T: type, value: []const u8) CoercedValue(T, @TypeOf(value)) {
|
||||
fn coerceBool(Adapter: type, T: type, value: []const u8) CoercedValue(Adapter, T, @TypeOf(value)) {
|
||||
if (value.len != 1) return .{ .err = error.JetQueryInvalidBooleanString };
|
||||
|
||||
const maybe_boolean = switch (value[0]) {
|
||||
|
||||
@ -62,7 +62,7 @@ pub fn FieldValues(Table: type, relations: []const type, comptime fields: []cons
|
||||
});
|
||||
}
|
||||
|
||||
pub fn ColumnType(Table: type, comptime field_info: FieldInfo) type {
|
||||
pub fn ColumnType(Adapter: type, Table: type, comptime field_info: FieldInfo) type {
|
||||
switch (field_info.context) {
|
||||
.limit, .offset => return usize,
|
||||
.where, .update, .insert, .order, .none => {},
|
||||
@ -70,7 +70,12 @@ pub fn ColumnType(Table: type, comptime field_info: FieldInfo) type {
|
||||
|
||||
if (comptime @hasField(Table.Definition, field_info.name)) {
|
||||
const FT = fieldType(Table.Definition, field_info.name);
|
||||
if (FT == jetcommon.types.DateTime) return i64 else return FT;
|
||||
return if (FT == jetcommon.types.DateTime)
|
||||
Adapter.DateTimePrimitive
|
||||
else if (FT == ?jetcommon.types.DateTime)
|
||||
?Adapter.DateTimePrimitive
|
||||
else
|
||||
FT;
|
||||
} else {
|
||||
// We only arrive here when we process triplets, e.g.
|
||||
// `.{ .foo, .lt_eql, 100 }`
|
||||
|
||||
@ -4,6 +4,7 @@ const fields = @import("../fields.zig");
|
||||
const coercion = @import("../coercion.zig");
|
||||
const columns = @import("../columns.zig");
|
||||
const sql = @import("../sql.zig");
|
||||
const DateTime = @import("jetcommon").types.DateTime;
|
||||
|
||||
const Where = @This();
|
||||
|
||||
@ -60,7 +61,7 @@ pub const Tree = struct {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn values(comptime self: Tree, args: anytype) ClauseValues(self.ValuesTuple, self.ErrorsTuple) {
|
||||
pub fn values(comptime self: Tree, Adapter: type, args: anytype) ClauseValues(self.ValuesTuple, self.ErrorsTuple) {
|
||||
var vals: self.ValuesTuple = undefined;
|
||||
var errors: self.ErrorsTuple = undefined;
|
||||
if (@typeInfo(@TypeOf(args)) != .@"struct") @compileError(
|
||||
@ -68,6 +69,7 @@ pub const Tree = struct {
|
||||
);
|
||||
assignValues(
|
||||
args,
|
||||
Adapter,
|
||||
self.ValuesTuple,
|
||||
&vals,
|
||||
self.ErrorsTuple,
|
||||
@ -79,10 +81,6 @@ pub const Tree = struct {
|
||||
return .{ .values = vals, .errors = errors };
|
||||
}
|
||||
|
||||
pub fn fields(comptime self: Tree) [self.root.countValues()]Field {
|
||||
return self.root.values_fields(self.Table, self.relations);
|
||||
}
|
||||
|
||||
pub fn countValues(comptime self: Tree) usize {
|
||||
return self.root.countValues();
|
||||
}
|
||||
@ -115,8 +113,8 @@ pub fn tree(
|
||||
.Table = Table,
|
||||
.relations = relations,
|
||||
.values_count = root.countValues(),
|
||||
.values_fields = &root.values_fields(Table, relations),
|
||||
.ValuesTuple = root.ValuesTuple(),
|
||||
.values_fields = &root.values_fields(Adapter, Table, relations),
|
||||
.ValuesTuple = root.ValuesTuple(Adapter),
|
||||
.ErrorsTuple = root.ErrorsTuple(),
|
||||
};
|
||||
}
|
||||
@ -134,18 +132,18 @@ pub const Node = union(enum) {
|
||||
index: usize,
|
||||
synthetic: bool = false,
|
||||
|
||||
pub fn ColumnType(self: Value) type {
|
||||
const T = fields.ColumnType(self.Table, fields.fieldInfo(
|
||||
pub fn ColumnType(self: Value, Adapter: type) type {
|
||||
const T = fields.ColumnType(Adapter, self.Table, fields.fieldInfo(
|
||||
self.field_info,
|
||||
self.Table,
|
||||
self.name,
|
||||
self.field_context,
|
||||
));
|
||||
return if (self.isArray()) []const T else T;
|
||||
return if (self.isArray(Adapter)) []const T else T;
|
||||
}
|
||||
|
||||
pub fn isArray(self: Value) bool {
|
||||
const T = fields.ColumnType(self.Table, fields.fieldInfo(
|
||||
pub fn isArray(self: Value, Adapter: type) bool {
|
||||
const T = fields.ColumnType(Adapter, self.Table, fields.fieldInfo(
|
||||
self.field_info,
|
||||
self.Table,
|
||||
self.name,
|
||||
@ -286,7 +284,7 @@ pub const Node = union(enum) {
|
||||
prefix,
|
||||
Adapter.identifier(value.Table.name),
|
||||
Adapter.identifier(value.name),
|
||||
if (value.isArray())
|
||||
if (value.isArray(Adapter))
|
||||
// XXX: This is PostgreSQL-specific - one day we'll need to figure
|
||||
// out how to generate SQL for unknown (at comptime) array length.
|
||||
// MySQL has `ANY` but it expects a subquery so maybe we'll need a
|
||||
@ -366,16 +364,17 @@ pub const Node = union(enum) {
|
||||
}
|
||||
}
|
||||
|
||||
fn ValuesTuple(comptime self: Node) type {
|
||||
fn ValuesTuple(comptime self: Node, Adapter: type) type {
|
||||
const len = self.countValues();
|
||||
var types: [len]type = undefined;
|
||||
var index: usize = 0;
|
||||
|
||||
appendValueType(self, len, &types, &index);
|
||||
appendValueType(Adapter, self, len, &types, &index);
|
||||
return std.meta.Tuple(&types);
|
||||
}
|
||||
|
||||
fn appendValueType(
|
||||
Adapter: type,
|
||||
comptime node: Node,
|
||||
comptime len: usize,
|
||||
types: *[len]type,
|
||||
@ -385,12 +384,12 @@ pub const Node = union(enum) {
|
||||
.condition => {},
|
||||
.value => |value| {
|
||||
if (!value.isNull()) {
|
||||
types[index.*] = value.ColumnType();
|
||||
types[index.*] = value.ColumnType(Adapter);
|
||||
index.* += 1;
|
||||
}
|
||||
},
|
||||
.group => |group| {
|
||||
for (group.children) |child| appendValueType(child, len, types, index);
|
||||
for (group.children) |child| appendValueType(Adapter, child, len, types, index);
|
||||
},
|
||||
.triplet => |triplet| {
|
||||
switch (triplet.lhs) {
|
||||
@ -420,7 +419,7 @@ pub const Node = union(enum) {
|
||||
.{sql_string.sql},
|
||||
));
|
||||
}
|
||||
appendValueType(value_node, len, types, index);
|
||||
appendValueType(Adapter, value_node, len, types, index);
|
||||
}
|
||||
},
|
||||
}
|
||||
@ -435,11 +434,16 @@ pub const Node = union(enum) {
|
||||
return std.meta.Tuple(&types);
|
||||
}
|
||||
|
||||
fn values_fields(comptime self: Node, Table: type, relations: []const type) [self.countValues()]Field {
|
||||
fn values_fields(
|
||||
comptime self: Node,
|
||||
Adapter: type,
|
||||
Table: type,
|
||||
relations: []const type,
|
||||
) [self.countValues()]Field {
|
||||
const len = self.countValues();
|
||||
var fields_array: [len]Field = undefined;
|
||||
var tuple_index: usize = 0;
|
||||
appendField(self, Table, relations, len, &fields_array, &tuple_index);
|
||||
appendField(self, Adapter, Table, relations, len, &fields_array, &tuple_index);
|
||||
|
||||
return fields_array;
|
||||
}
|
||||
@ -467,6 +471,7 @@ pub const Node = union(enum) {
|
||||
|
||||
fn appendField(
|
||||
node: Node,
|
||||
Adapter: type,
|
||||
Table: type,
|
||||
relations: []const type,
|
||||
comptime len: usize,
|
||||
@ -476,11 +481,11 @@ pub const Node = union(enum) {
|
||||
switch (node) {
|
||||
.condition => {},
|
||||
.value => |value| {
|
||||
appendValueField(value.ColumnType(), value, len, fields_array, tuple_index);
|
||||
appendValueField(value.ColumnType(Adapter), value, len, fields_array, tuple_index);
|
||||
},
|
||||
.group => |group| {
|
||||
for (group.children) |child| {
|
||||
appendField(child, Table, relations, len, fields_array, tuple_index);
|
||||
appendField(child, Adapter, Table, relations, len, fields_array, tuple_index);
|
||||
}
|
||||
},
|
||||
.triplet => |triplet| {
|
||||
@ -574,7 +579,20 @@ fn nodeTree(
|
||||
}
|
||||
|
||||
return switch (@typeInfo(T)) {
|
||||
.@"struct" => |info| if (isTriplet(T)) blk: {
|
||||
.@"struct" => |info| if (T == DateTime) blk: {
|
||||
const value = Node.Value{
|
||||
.field_context = field_context,
|
||||
.Table = findRelation(Table, relations, path),
|
||||
.name = name,
|
||||
.type = Adapter.DateTimePrimitive,
|
||||
.source_type = T,
|
||||
.field_info = field_info,
|
||||
.path = makePath(path, null),
|
||||
.index = value_index.*,
|
||||
};
|
||||
value_index.* += 1;
|
||||
break :blk .{ .value = value };
|
||||
} else if (isTriplet(T)) blk: {
|
||||
break :blk .{ .triplet = makeTriplet(
|
||||
Adapter,
|
||||
Table,
|
||||
@ -711,6 +729,7 @@ fn findRelation(Table: type, relations: []const type, comptime path: []const []c
|
||||
|
||||
fn assignValues(
|
||||
arg: anytype,
|
||||
Adapter: type,
|
||||
ValuesTuple: type,
|
||||
values_tuple: *ValuesTuple,
|
||||
ErrorsTuple: type,
|
||||
@ -722,6 +741,7 @@ fn assignValues(
|
||||
if (comptime coercion.canCoerceDelegate(@TypeOf(arg))) {
|
||||
assignValue(
|
||||
arg,
|
||||
Adapter,
|
||||
ValuesTuple,
|
||||
values_tuple,
|
||||
ErrorsTuple,
|
||||
@ -737,9 +757,22 @@ fn assignValues(
|
||||
.@"struct" => |info| {
|
||||
// Note that we will always land here first because the first arg to `where` is is
|
||||
// always a struct, so we can safely start here for incrementing our index counter.
|
||||
if (comptime isSqlStringWithArgsArray(@TypeOf(arg))) {
|
||||
if (comptime @TypeOf(arg) == DateTime) {
|
||||
assignValue(
|
||||
arg,
|
||||
Adapter,
|
||||
ValuesTuple,
|
||||
values_tuple,
|
||||
ErrorsTuple,
|
||||
errors_tuple,
|
||||
values_fields,
|
||||
path,
|
||||
coerce,
|
||||
);
|
||||
} else if (comptime isSqlStringWithArgsArray(@TypeOf(arg))) {
|
||||
assignValues(
|
||||
arg[1],
|
||||
Adapter,
|
||||
ValuesTuple,
|
||||
values_tuple,
|
||||
ErrorsTuple,
|
||||
@ -755,6 +788,7 @@ fn assignValues(
|
||||
// index 2).
|
||||
assignValues(
|
||||
@field(arg, field.name)[2],
|
||||
Adapter,
|
||||
ValuesTuple,
|
||||
values_tuple,
|
||||
ErrorsTuple,
|
||||
@ -767,6 +801,7 @@ fn assignValues(
|
||||
// Recurse to evaluate whatever exists inside this struct.
|
||||
assignValues(
|
||||
@field(arg, field.name),
|
||||
Adapter,
|
||||
ValuesTuple,
|
||||
values_tuple,
|
||||
ErrorsTuple,
|
||||
@ -792,6 +827,7 @@ fn assignValues(
|
||||
else => {
|
||||
assignValue(
|
||||
arg,
|
||||
Adapter,
|
||||
ValuesTuple,
|
||||
values_tuple,
|
||||
ErrorsTuple,
|
||||
@ -806,6 +842,7 @@ fn assignValues(
|
||||
|
||||
fn assignValue(
|
||||
arg: anytype,
|
||||
Adapter: type,
|
||||
ValuesTuple: type,
|
||||
values_tuple: *ValuesTuple,
|
||||
ErrorsTuple: type,
|
||||
@ -836,9 +873,11 @@ fn assignValue(
|
||||
);
|
||||
if (comptime coerce) {
|
||||
const coerced: coercion.CoercedValue(
|
||||
Adapter,
|
||||
value_field.column_type,
|
||||
@TypeOf(arg),
|
||||
) = coercion.coerce(
|
||||
Adapter,
|
||||
value_field.Table,
|
||||
field_info,
|
||||
arg,
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user