Allow arrays as model key in repository find function.
This commit is contained in:
parent
0eecb4df99
commit
7734cea9d0
3 changed files with 144 additions and 19 deletions
|
@ -2,6 +2,7 @@ const std = @import("std");
|
||||||
const pg = @import("pg");
|
const pg = @import("pg");
|
||||||
const zollections = @import("zollections");
|
const zollections = @import("zollections");
|
||||||
const _sql = @import("sql.zig");
|
const _sql = @import("sql.zig");
|
||||||
|
const _conditions = @import("conditions.zig");
|
||||||
const query = @import("query.zig");
|
const query = @import("query.zig");
|
||||||
const insert = @import("insert.zig");
|
const insert = @import("insert.zig");
|
||||||
const update = @import("update.zig");
|
const update = @import("update.zig");
|
||||||
|
@ -34,6 +35,41 @@ pub fn RepositoryConfiguration(comptime Model: type, comptime TableShape: type)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Build the type of a model key, based on the given configuration.
|
||||||
|
pub fn ModelKeyType(comptime Model: type, comptime TableShape: type, comptime config: RepositoryConfiguration(Model, TableShape)) type {
|
||||||
|
if (config.key.len == 0) {
|
||||||
|
// Get the type of the simple key.
|
||||||
|
return std.meta.fields(TableShape)[std.meta.fieldIndex(TableShape, config.key[0]).?].type;
|
||||||
|
} else {
|
||||||
|
// Build the type of the composite key.
|
||||||
|
|
||||||
|
// Build key fields.
|
||||||
|
var fields: [config.key.len]std.builtin.Type.StructField = undefined;
|
||||||
|
inline for (config.key, &fields) |keyName, *field| {
|
||||||
|
// Build NULL-terminated key name as field name.
|
||||||
|
var fieldName: [keyName.len:0]u8 = undefined;
|
||||||
|
@memcpy(fieldName[0..keyName.len], keyName);
|
||||||
|
|
||||||
|
field.* = .{
|
||||||
|
.name = &fieldName,
|
||||||
|
.type = std.meta.fields(TableShape)[std.meta.fieldIndex(TableShape, keyName).?].type,
|
||||||
|
.default_value = null,
|
||||||
|
.is_comptime = false,
|
||||||
|
.alignment = 0,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return @Type(.{
|
||||||
|
.Struct = std.builtin.Type.Struct{
|
||||||
|
.layout = std.builtin.Type.ContainerLayout.auto,
|
||||||
|
.fields = &fields,
|
||||||
|
.decls = &[_]std.builtin.Type.Declaration{},
|
||||||
|
.is_tuple = false,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Repository of structures of a certain type.
|
/// Repository of structures of a certain type.
|
||||||
pub fn Repository(comptime Model: type, comptime TableShape: type, comptime config: RepositoryConfiguration(Model, TableShape)) type {
|
pub fn Repository(comptime Model: type, comptime TableShape: type, comptime config: RepositoryConfiguration(Model, TableShape)) type {
|
||||||
return struct {
|
return struct {
|
||||||
|
@ -42,6 +78,9 @@ pub fn Repository(comptime Model: type, comptime TableShape: type, comptime conf
|
||||||
pub const Query: type = query.RepositoryQuery(Model, TableShape, config);
|
pub const Query: type = query.RepositoryQuery(Model, TableShape, config);
|
||||||
pub const Insert: type = insert.RepositoryInsert(Model, TableShape, config, config.insertShape);
|
pub const Insert: type = insert.RepositoryInsert(Model, TableShape, config, config.insertShape);
|
||||||
|
|
||||||
|
/// Type of one model key.
|
||||||
|
pub const KeyType = ModelKeyType(Model, TableShape, config);
|
||||||
|
|
||||||
pub fn InsertCustom(comptime InsertShape: type) type {
|
pub fn InsertCustom(comptime InsertShape: type) type {
|
||||||
return insert.RepositoryInsert(Model, TableShape, config, InsertShape);
|
return insert.RepositoryInsert(Model, TableShape, config, InsertShape);
|
||||||
}
|
}
|
||||||
|
@ -51,34 +90,70 @@ pub fn Repository(comptime Model: type, comptime TableShape: type, comptime conf
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Try to find the requested model.
|
/// Try to find the requested model.
|
||||||
|
/// For simple keys: modelKey type must match the type of its corresponding field.
|
||||||
|
/// modelKey can be an array / slice of keys.
|
||||||
|
/// For composite keys: modelKey must be a struct with all the keys, matching the type of their corresponding field.
|
||||||
|
/// modelKey can be an array / slice of these structs.
|
||||||
pub fn find(allocator: std.mem.Allocator, database: *pg.Pool, modelKey: anytype) !RepositoryResult(Model) {
|
pub fn find(allocator: std.mem.Allocator, database: *pg.Pool, modelKey: anytype) !RepositoryResult(Model) {
|
||||||
// Initialize a new query.
|
// Initialize a new query.
|
||||||
var modelQuery = Self.Query.init(allocator, database, .{});
|
var modelQuery = Self.Query.init(allocator, database, .{});
|
||||||
defer modelQuery.deinit();
|
defer modelQuery.deinit();
|
||||||
|
|
||||||
if (config.key.len == 1) {
|
if (config.key.len == 1) {
|
||||||
// Add a simple condition.
|
// Find key name and its type.
|
||||||
try modelQuery.whereValue(std.meta.fields(TableShape)[std.meta.fieldIndex(TableShape, config.key[0]).?].type, config.key[0], "=", modelKey);
|
const keyName = config.key[0];
|
||||||
} else {
|
const keyType = std.meta.fields(TableShape)[std.meta.fieldIndex(TableShape, keyName).?].type;
|
||||||
// Add conditions for all keys in the composite key.
|
|
||||||
var conditions: [config.key.len]_sql.SqlParams = undefined;
|
|
||||||
|
|
||||||
inline for (config.key, &conditions) |keyName, *condition| {
|
// Accept arrays / slices of keys, and simple keys.
|
||||||
if (std.meta.fieldIndex(@TypeOf(modelKey), keyName)) |_| {
|
switch (@typeInfo(@TypeOf(modelKey))) {
|
||||||
// The field exists in the key structure, create its condition.
|
.Pointer => |ptr| {
|
||||||
condition.* = try modelQuery.newCondition().value(
|
switch (ptr.size) {
|
||||||
std.meta.fields(TableShape)[std.meta.fieldIndex(TableShape, keyName).?].type,
|
.One => {
|
||||||
keyName, "=",
|
switch (@typeInfo(ptr.child)) {
|
||||||
@field(modelKey, keyName),
|
// Add a whereIn with the array.
|
||||||
);
|
.Array => try modelQuery.whereIn(keyType, keyName, modelKey),
|
||||||
} else {
|
// Add a simple condition with the pointed value.
|
||||||
// The field doesn't exist, compilation error.
|
else => try modelQuery.whereValue(keyType, keyName, "=", modelKey.*),
|
||||||
@compileError("The key structure must include a field for " ++ keyName);
|
}
|
||||||
}
|
},
|
||||||
|
// Add a whereIn with the slice.
|
||||||
|
else => try modelQuery.whereIn(keyType, keyName, modelKey),
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// Add a simple condition with the given value.
|
||||||
|
else => try modelQuery.whereValue(keyType, keyName, "=", modelKey),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Accept arrays / slices of keys, and simple keys.
|
||||||
|
// Uniformize modelKey parameter to a slice.
|
||||||
|
const modelKeysList: []const Self.KeyType = switch (@typeInfo(@TypeOf(modelKey))) {
|
||||||
|
.Pointer => |ptr| switch (ptr.size) {
|
||||||
|
.One => switch (@typeInfo(ptr.child)) {
|
||||||
|
// Already an array.
|
||||||
|
.Array => @as([]const Self.KeyType, modelKey),
|
||||||
|
// Convert the pointer to an array.
|
||||||
|
else => &[1]Self.KeyType{@as(Self.KeyType, modelKey.*)},
|
||||||
|
},
|
||||||
|
// Already a slice.
|
||||||
|
else => @as([]const Self.KeyType, modelKey),
|
||||||
|
},
|
||||||
|
// Convert the value to an array.
|
||||||
|
else => &[1]Self.KeyType{@as(Self.KeyType, modelKey)},
|
||||||
|
};
|
||||||
|
|
||||||
|
// Initialize keys conditions list.
|
||||||
|
const conditions: []_sql.SqlParams = try allocator.alloc(_sql.SqlParams, modelKeysList.len);
|
||||||
|
defer allocator.free(conditions);
|
||||||
|
|
||||||
|
// For each model key, add its conditions.
|
||||||
|
for (modelKeysList, conditions) |_modelKey, *condition| {
|
||||||
|
condition.* = try modelQuery.newCondition().@"and"(
|
||||||
|
&try buildCompositeKeysConditions(TableShape, config.key, modelQuery.newCondition(), _modelKey)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set WHERE conditions in the query.
|
// Set WHERE conditions in the query with all keys conditions.
|
||||||
modelQuery.where(try modelQuery.newCondition().@"and"(&conditions));
|
modelQuery.where(try modelQuery.newCondition().@"or"(conditions));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Execute query and return its result.
|
// Execute query and return its result.
|
||||||
|
@ -179,3 +254,24 @@ pub fn RepositoryResult(comptime Model: type) type {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Build conditions for given composite keys, with a model key structure.
|
||||||
|
pub fn buildCompositeKeysConditions(comptime TableShape: type, comptime keys: []const []const u8, conditionsBuilder: _conditions.Builder, modelKey: anytype) ![keys.len]_sql.SqlParams {
|
||||||
|
// Conditions list for all keys in the composite key.
|
||||||
|
var conditions: [keys.len]_sql.SqlParams = undefined;
|
||||||
|
|
||||||
|
inline for (keys, &conditions) |keyName, *condition| {
|
||||||
|
const keyType = std.meta.fields(TableShape)[std.meta.fieldIndex(TableShape, keyName).?].type;
|
||||||
|
|
||||||
|
if (std.meta.fieldIndex(@TypeOf(modelKey), keyName)) |_| {
|
||||||
|
// The field exists in the key structure, create its condition.
|
||||||
|
condition.* = try conditionsBuilder.value(keyType, keyName, "=", @field(modelKey, keyName));
|
||||||
|
} else {
|
||||||
|
// The field doesn't exist, compilation error.
|
||||||
|
@compileError("The key structure must include a field for " ++ keyName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return conditions for the current model key.
|
||||||
|
return conditions;
|
||||||
|
}
|
||||||
|
|
|
@ -136,4 +136,24 @@ test "composite model create, save and find" {
|
||||||
|
|
||||||
try std.testing.expectEqual(1, result4.models.len);
|
try std.testing.expectEqual(1, result4.models.len);
|
||||||
try std.testing.expectEqualDeep(newModel, result4.first().?.*);
|
try std.testing.expectEqualDeep(newModel, result4.first().?.*);
|
||||||
|
|
||||||
|
|
||||||
|
// Try to find multiple models at once.
|
||||||
|
var result5 = try CompositeModelRepository.find(std.testing.allocator, database, &[_]CompositeModelRepository.KeyType{
|
||||||
|
.{
|
||||||
|
.firstcol = newModel.firstcol,
|
||||||
|
.secondcol = newModel.secondcol,
|
||||||
|
},
|
||||||
|
.{
|
||||||
|
.firstcol = result3.first().?.firstcol,
|
||||||
|
.secondcol = result3.first().?.secondcol,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
defer result5.deinit();
|
||||||
|
|
||||||
|
try std.testing.expectEqual(2, result5.models.len);
|
||||||
|
try std.testing.expectEqual(newModel.firstcol, result5.models[0].firstcol);
|
||||||
|
try std.testing.expectEqualStrings(newModel.secondcol, result5.models[0].secondcol);
|
||||||
|
try std.testing.expectEqual(result3.first().?.firstcol, result5.models[1].firstcol);
|
||||||
|
try std.testing.expectEqualStrings(result3.first().?.secondcol, result5.models[1].secondcol);
|
||||||
}
|
}
|
||||||
|
|
|
@ -310,4 +310,13 @@ test "model create, save and find" {
|
||||||
defer result4.deinit(); // Will clear some values in newModel.
|
defer result4.deinit(); // Will clear some values in newModel.
|
||||||
|
|
||||||
try std.testing.expectEqualDeep(newModel, result4.first().?.*);
|
try std.testing.expectEqualDeep(newModel, result4.first().?.*);
|
||||||
|
|
||||||
|
|
||||||
|
// Try to find multiple models at once.
|
||||||
|
var result5 = try MyModelRepository.find(std.testing.allocator, database, &[_]i32{1, newModel.id});
|
||||||
|
defer result5.deinit();
|
||||||
|
|
||||||
|
try std.testing.expectEqual(2, result5.models.len);
|
||||||
|
try std.testing.expectEqual(1, result5.models[0].id);
|
||||||
|
try std.testing.expectEqual(newModel.id, result5.models[1].id);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue