Load relations which are not inline.
+ Allow to retrieve metadata along with models. + Get all unloaded relations after model mapping. + Test hasMany relations. + Separate runtime and comptime relations instances. TODO: improve this. * Fix and improve relations query building. * Fix comptime select build called in runtime functions.
This commit is contained in:
parent
24989603a3
commit
b3007a1b5d
10 changed files with 335 additions and 78 deletions
|
@ -120,7 +120,7 @@ pub fn RepositoryInsert(comptime Model: type, comptime TableShape: type, comptim
|
||||||
const Configuration = RepositoryInsertConfiguration(InsertShape);
|
const Configuration = RepositoryInsertConfiguration(InsertShape);
|
||||||
|
|
||||||
/// Result mapper type.
|
/// Result mapper type.
|
||||||
pub const ResultMapper = _result.ResultMapper(Model, TableShape, repositoryConfig, null, null);
|
pub const ResultMapper = _result.ResultMapper(Model, TableShape, null, repositoryConfig, null, null);
|
||||||
|
|
||||||
arena: std.heap.ArenaAllocator,
|
arena: std.heap.ArenaAllocator,
|
||||||
connector: database.Connector,
|
connector: database.Connector,
|
||||||
|
@ -336,8 +336,8 @@ pub fn RepositoryInsert(comptime Model: type, comptime TableShape: type, comptim
|
||||||
defer queryResult.deinit();
|
defer queryResult.deinit();
|
||||||
|
|
||||||
// Map query results.
|
// Map query results.
|
||||||
var postgresqlReader = postgresql.QueryResultReader(TableShape, null).init(queryResult);
|
var postgresqlReader = postgresql.QueryResultReader(TableShape, null, null).init(queryResult);
|
||||||
return try ResultMapper.map(allocator, postgresqlReader.reader());
|
return try ResultMapper.map(false, allocator, self.connector, postgresqlReader.reader());
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Initialize a new repository insert query.
|
/// Initialize a new repository insert query.
|
||||||
|
|
|
@ -162,8 +162,8 @@ pub fn makeMapper(comptime T: type, result: *pg.Result, allocator: std.mem.Alloc
|
||||||
}
|
}
|
||||||
|
|
||||||
/// PostgreSQL implementation of the query result reader.
|
/// PostgreSQL implementation of the query result reader.
|
||||||
pub fn QueryResultReader(comptime TableShape: type, comptime inlineRelations: ?[]const _relations.ModelRelation) type {
|
pub fn QueryResultReader(comptime TableShape: type, comptime MetadataShape: ?type, comptime inlineRelations: ?[]const _relations.ModelRelation) type {
|
||||||
const InstanceInterface = _result.QueryResultReader(TableShape, inlineRelations).Instance;
|
const InstanceInterface = _result.QueryResultReader(TableShape, MetadataShape, inlineRelations).Instance;
|
||||||
|
|
||||||
// Build relations mappers container type.
|
// Build relations mappers container type.
|
||||||
const RelationsMappersType = comptime typeBuilder: {
|
const RelationsMappersType = comptime typeBuilder: {
|
||||||
|
@ -215,9 +215,10 @@ pub fn QueryResultReader(comptime TableShape: type, comptime inlineRelations: ?[
|
||||||
pub const Instance = struct {
|
pub const Instance = struct {
|
||||||
/// Main object mapper.
|
/// Main object mapper.
|
||||||
mainMapper: PgMapper(TableShape) = undefined,
|
mainMapper: PgMapper(TableShape) = undefined,
|
||||||
|
metadataMapper: PgMapper(MetadataShape orelse struct {}) = undefined,
|
||||||
relationsMappers: RelationsMappersType = undefined,
|
relationsMappers: RelationsMappersType = undefined,
|
||||||
|
|
||||||
fn next(opaqueSelf: *anyopaque) !?_result.TableWithRelations(TableShape, inlineRelations) {
|
fn next(opaqueSelf: *anyopaque) !?_result.TableWithRelations(TableShape, MetadataShape, inlineRelations) {
|
||||||
const self: *Instance = @ptrCast(@alignCast(opaqueSelf));
|
const self: *Instance = @ptrCast(@alignCast(opaqueSelf));
|
||||||
|
|
||||||
// Try to get the next row.
|
// Try to get the next row.
|
||||||
|
@ -227,7 +228,7 @@ pub fn QueryResultReader(comptime TableShape: type, comptime inlineRelations: ?[
|
||||||
const mainTable = try self.mainMapper.next(&row) orelse return null;
|
const mainTable = try self.mainMapper.next(&row) orelse return null;
|
||||||
|
|
||||||
// Initialize the result.
|
// Initialize the result.
|
||||||
var result: _result.TableWithRelations(TableShape, inlineRelations) = undefined;
|
var result: _result.TableWithRelations(TableShape, MetadataShape, inlineRelations) = undefined;
|
||||||
|
|
||||||
// Copy each basic table field.
|
// Copy each basic table field.
|
||||||
inline for (std.meta.fields(TableShape)) |field| {
|
inline for (std.meta.fields(TableShape)) |field| {
|
||||||
|
@ -242,6 +243,10 @@ pub fn QueryResultReader(comptime TableShape: type, comptime inlineRelations: ?[
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (MetadataShape) |_| {
|
||||||
|
result._zrm_metadata = (try self.metadataMapper.next(&row)).?;
|
||||||
|
}
|
||||||
|
|
||||||
return result; // Return built result.
|
return result; // Return built result.
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -266,6 +271,9 @@ pub fn QueryResultReader(comptime TableShape: type, comptime inlineRelations: ?[
|
||||||
fn initInstance(opaqueSelf: *anyopaque, allocator: std.mem.Allocator) !InstanceInterface {
|
fn initInstance(opaqueSelf: *anyopaque, allocator: std.mem.Allocator) !InstanceInterface {
|
||||||
const self: *Self = @ptrCast(@alignCast(opaqueSelf));
|
const self: *Self = @ptrCast(@alignCast(opaqueSelf));
|
||||||
self.instance.mainMapper = try makeMapper(TableShape, self.result, allocator, null);
|
self.instance.mainMapper = try makeMapper(TableShape, self.result, allocator, null);
|
||||||
|
if (MetadataShape) |MetadataType| {
|
||||||
|
self.instance.metadataMapper = try makeMapper(MetadataType, self.result, allocator, null);
|
||||||
|
}
|
||||||
|
|
||||||
if (inlineRelations) |_inlineRelations| {
|
if (inlineRelations) |_inlineRelations| {
|
||||||
// Initialize mapper for each relation.
|
// Initialize mapper for each relation.
|
||||||
|
@ -282,7 +290,7 @@ pub fn QueryResultReader(comptime TableShape: type, comptime inlineRelations: ?[
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get the generic reader instance.
|
/// Get the generic reader instance.
|
||||||
pub fn reader(self: *Self) _result.QueryResultReader(TableShape, inlineRelations) {
|
pub fn reader(self: *Self) _result.QueryResultReader(TableShape, MetadataShape, inlineRelations) {
|
||||||
return .{
|
return .{
|
||||||
._interface = .{
|
._interface = .{
|
||||||
.instance = self,
|
.instance = self,
|
||||||
|
|
|
@ -28,10 +28,12 @@ const CompiledRelations = struct {
|
||||||
|
|
||||||
/// Repository models query manager.
|
/// Repository models query manager.
|
||||||
/// Manage query string build and its execution.
|
/// Manage query string build and its execution.
|
||||||
pub fn RepositoryQuery(comptime Model: type, comptime TableShape: type, comptime repositoryConfig: repository.RepositoryConfiguration(Model, TableShape), comptime with: ?[]const relations.ModelRelation) type {
|
pub fn RepositoryQuery(comptime Model: type, comptime TableShape: type, comptime repositoryConfig: repository.RepositoryConfiguration(Model, TableShape), comptime with: ?[]const relations.ModelRelation, comptime MetadataShape: ?type) type {
|
||||||
const compiledRelations = comptime compile: {
|
const compiledRelations = comptime compile: {
|
||||||
// Inline relations list.
|
// Inline relations list.
|
||||||
var inlineRelations: []relations.ModelRelation = &[0]relations.ModelRelation{};
|
var inlineRelations: []relations.ModelRelation = &[0]relations.ModelRelation{};
|
||||||
|
// Other relations list.
|
||||||
|
var otherRelations: []relations.ModelRelation = &[0]relations.ModelRelation{};
|
||||||
|
|
||||||
if (with) |_with| {
|
if (with) |_with| {
|
||||||
// If there are relations to eager load, prepare their query.
|
// If there are relations to eager load, prepare their query.
|
||||||
|
@ -57,12 +59,15 @@ pub fn RepositoryQuery(comptime Model: type, comptime TableShape: type, comptime
|
||||||
inlineSelect = @ptrCast(@constCast(_comptime.append(inlineSelect, relationInstance.genSelect(tableAlias, fieldsPrefix))));
|
inlineSelect = @ptrCast(@constCast(_comptime.append(inlineSelect, relationInstance.genSelect(tableAlias, fieldsPrefix))));
|
||||||
// Generate joined table for the relation.
|
// Generate joined table for the relation.
|
||||||
inlineJoins = @ptrCast(@constCast(_comptime.append(inlineJoins, relationInstance.genJoin(tableAlias))));
|
inlineJoins = @ptrCast(@constCast(_comptime.append(inlineJoins, relationInstance.genJoin(tableAlias))));
|
||||||
|
} else {
|
||||||
|
// Add the current relation to other relations.
|
||||||
|
otherRelations = @ptrCast(@constCast(_comptime.append(otherRelations, relation)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
break :compile CompiledRelations{
|
break :compile CompiledRelations{
|
||||||
.inlineRelations = inlineRelations,
|
.inlineRelations = inlineRelations,
|
||||||
.otherRelations = &[0]relations.ModelRelation{},
|
.otherRelations = otherRelations,
|
||||||
.inlineSelect = if (inlineSelect.len > 0) ", " ++ _comptime.join(", ", inlineSelect) else "",
|
.inlineSelect = if (inlineSelect.len > 0) ", " ++ _comptime.join(", ", inlineSelect) else "",
|
||||||
.inlineJoins = if (inlineJoins.len > 0) " " ++ _comptime.join(" ", inlineJoins) else "",
|
.inlineJoins = if (inlineJoins.len > 0) " " ++ _comptime.join(" ", inlineJoins) else "",
|
||||||
};
|
};
|
||||||
|
@ -88,7 +93,7 @@ pub fn RepositoryQuery(comptime Model: type, comptime TableShape: type, comptime
|
||||||
const Self = @This();
|
const Self = @This();
|
||||||
|
|
||||||
/// Result mapper type.
|
/// Result mapper type.
|
||||||
pub const ResultMapper = _result.ResultMapper(Model, TableShape, repositoryConfig, compiledRelations.inlineRelations, compiledRelations.otherRelations);
|
pub const ResultMapper = _result.ResultMapper(Model, TableShape, MetadataShape, repositoryConfig, compiledRelations.inlineRelations, compiledRelations.otherRelations);
|
||||||
|
|
||||||
arena: std.heap.ArenaAllocator,
|
arena: std.heap.ArenaAllocator,
|
||||||
connector: database.Connector,
|
connector: database.Connector,
|
||||||
|
@ -285,8 +290,8 @@ pub fn RepositoryQuery(comptime Model: type, comptime TableShape: type, comptime
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Retrieve queried models.
|
/// Generic queried models retrieval.
|
||||||
pub fn get(self: *Self, allocator: std.mem.Allocator) !repository.RepositoryResult(Model) {
|
fn _get(self: *Self, allocator: std.mem.Allocator, comptime withMetadata: bool) !repository.RepositoryResult(if (withMetadata) _result.ModelWithMetadata(Model, MetadataShape) else Model) {
|
||||||
// Build SQL query if it wasn't built.
|
// Build SQL query if it wasn't built.
|
||||||
if (self.sql) |_| {} else { try self.buildSql(); }
|
if (self.sql) |_| {} else { try self.buildSql(); }
|
||||||
|
|
||||||
|
@ -296,8 +301,22 @@ pub fn RepositoryQuery(comptime Model: type, comptime TableShape: type, comptime
|
||||||
defer queryResult.deinit();
|
defer queryResult.deinit();
|
||||||
|
|
||||||
// Map query results.
|
// Map query results.
|
||||||
var postgresqlReader = postgresql.QueryResultReader(TableShape, compiledRelations.inlineRelations).init(queryResult);
|
var postgresqlReader = postgresql.QueryResultReader(TableShape, MetadataShape, compiledRelations.inlineRelations).init(queryResult);
|
||||||
return try ResultMapper.map(allocator, postgresqlReader.reader());
|
return try ResultMapper.map(withMetadata, allocator, self.connector, postgresqlReader.reader());
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Retrieve queried models.
|
||||||
|
pub fn get(self: *Self, allocator: std.mem.Allocator) !repository.RepositoryResult(Model) {
|
||||||
|
return self._get(allocator, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Retrieved queries models with metadata.
|
||||||
|
pub fn getWithMetadata(self: *Self, allocator: std.mem.Allocator) !repository.RepositoryResult(_result.ModelWithMetadata(Model, MetadataShape)) {
|
||||||
|
if (MetadataShape) |_| {
|
||||||
|
return self._get(allocator, true);
|
||||||
|
} else {
|
||||||
|
unreachable;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Initialize a new repository query.
|
/// Initialize a new repository query.
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
const pg = @import("pg");
|
const pg = @import("pg");
|
||||||
|
const _database = @import("database.zig");
|
||||||
const _sql = @import("sql.zig");
|
const _sql = @import("sql.zig");
|
||||||
const repository = @import("repository.zig");
|
const repository = @import("repository.zig");
|
||||||
const _query = @import("query.zig");
|
const _query = @import("query.zig");
|
||||||
|
@ -62,7 +63,9 @@ pub fn typedMany(
|
||||||
};
|
};
|
||||||
|
|
||||||
const FromKeyType = std.meta.fields(FromModel)[std.meta.fieldIndex(FromModel, fromRepositoryConfig.key[0]).?].type;
|
const FromKeyType = std.meta.fields(FromModel)[std.meta.fieldIndex(FromModel, fromRepositoryConfig.key[0]).?].type;
|
||||||
const QueryType = _query.RepositoryQuery(ToModel, ToTable, toRepositoryConfig, null);
|
const QueryType = _query.RepositoryQuery(ToModel, ToTable, toRepositoryConfig, null, struct {
|
||||||
|
__zrm_relation_key: FromKeyType,
|
||||||
|
});
|
||||||
|
|
||||||
return struct {
|
return struct {
|
||||||
const Self = @This();
|
const Self = @This();
|
||||||
|
@ -79,15 +82,29 @@ pub fn typedMany(
|
||||||
unreachable; // No possible join in a many relation.
|
unreachable; // No possible join in a many relation.
|
||||||
}
|
}
|
||||||
|
|
||||||
fn genSelect(_: *anyopaque, comptime table: []const u8, comptime prefix: []const u8) []const u8 {
|
fn _genSelect(comptime table: []const u8, comptime prefix: []const u8) []const u8 {
|
||||||
return _sql.SelectBuild(ToTable, table, prefix);
|
return _sql.SelectBuild(ToTable, table, prefix);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn buildQuery(_: *anyopaque, opaqueModels: []const anyopaque, opaqueQuery: *anyopaque) !void {
|
fn genSelect(_: *anyopaque, comptime table: []const u8, comptime prefix: []const u8) []const u8 {
|
||||||
var models: []const FromModel = undefined;
|
return _genSelect(table, prefix);
|
||||||
models.len = opaqueModels.len;
|
}
|
||||||
models.ptr = @ptrCast(@alignCast(opaqueModels.ptr));
|
|
||||||
const query: *QueryType = @ptrCast(@alignCast(opaqueQuery));
|
fn getQueryType() type {
|
||||||
|
return QueryType;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn buildQuery(_: *anyopaque, prefix: []const u8, opaqueModels: []const *anyopaque, allocator: std.mem.Allocator, connector: _database.Connector) !*anyopaque {
|
||||||
|
const models: []const *FromModel = @ptrCast(@alignCast(opaqueModels));
|
||||||
|
|
||||||
|
// Initialize the query to build.
|
||||||
|
const query: *QueryType = try allocator.create(QueryType);
|
||||||
|
errdefer allocator.destroy(query);
|
||||||
|
query.* = QueryType.init(allocator, connector, .{});
|
||||||
|
errdefer query.deinit();
|
||||||
|
|
||||||
|
// Build base SELECT.
|
||||||
|
const baseSelect = comptime _genSelect(toRepositoryConfig.table, "");
|
||||||
|
|
||||||
// Prepare given models IDs.
|
// Prepare given models IDs.
|
||||||
const modelsIds = try query.arena.allocator().alloc(FromKeyType, models.len);
|
const modelsIds = try query.arena.allocator().alloc(FromKeyType, models.len);
|
||||||
|
@ -97,20 +114,34 @@ pub fn typedMany(
|
||||||
|
|
||||||
switch (config) {
|
switch (config) {
|
||||||
.direct => {
|
.direct => {
|
||||||
// Build WHERE condition.
|
// Add SELECT.
|
||||||
try query.whereIn(FromKeyType, "\"" ++ toRepositoryConfig.table ++ "\".\"" ++ foreignKey ++ "\"", modelsIds);
|
query.select(.{
|
||||||
},
|
.sql = baseSelect ++ ", \"" ++ toRepositoryConfig.table ++ "\".\"" ++ foreignKey ++ "\" AS \"__zrm_relation_key\"",
|
||||||
.through => |through| {
|
|
||||||
query.join(.{
|
|
||||||
.sql = "INNER JOIN \"" ++ through.table ++ "\" ON " ++
|
|
||||||
"\"" ++ toRepositoryConfig.table ++ "\"." ++ modelKey ++ " = " ++ "\"" ++ through.table ++ "\"." ++ through.joinModelKey,
|
|
||||||
.params = &[0]_sql.RawQueryParameter{},
|
.params = &[0]_sql.RawQueryParameter{},
|
||||||
});
|
});
|
||||||
|
|
||||||
// Build WHERE condition.
|
// Build WHERE condition.
|
||||||
try query.whereIn(FromKeyType, "\"" ++ through.table ++ "\".\"" ++ through.joinForeignKey ++ "\"", modelsIds);
|
try query.whereIn(FromKeyType, "\"" ++ toRepositoryConfig.table ++ "\".\"" ++ foreignKey ++ "\"", modelsIds);
|
||||||
|
},
|
||||||
|
.through => |through| {
|
||||||
|
// Add SELECT.
|
||||||
|
query.select(.{
|
||||||
|
.sql = try std.fmt.allocPrint(query.arena.allocator(), baseSelect ++ ", \"{s}pivot" ++ "\".\"" ++ through.joinForeignKey ++ "\" AS \"__zrm_relation_key\"", .{prefix}),
|
||||||
|
.params = &[0]_sql.RawQueryParameter{},
|
||||||
|
});
|
||||||
|
|
||||||
|
query.join(.{
|
||||||
|
.sql = try std.fmt.allocPrint(query.arena.allocator(), "INNER JOIN \"" ++ through.table ++ "\" ON AS \"{s}pivot" ++ "\" " ++
|
||||||
|
"\"" ++ toRepositoryConfig.table ++ "\"." ++ modelKey ++ " = " ++ "\"{s}pivot" ++ "\"." ++ through.joinModelKey, .{prefix, prefix}),
|
||||||
|
.params = &[0]_sql.RawQueryParameter{},
|
||||||
|
});
|
||||||
|
|
||||||
|
// Build WHERE condition.
|
||||||
|
try query.whereIn(FromKeyType, try std.fmt.allocPrint(query.arena.allocator(), "\"{s}pivot" ++ "\".\"" ++ through.joinForeignKey ++ "\"", .{prefix}), modelsIds);
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return query; // Return built query.
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn relation(self: *Self) Relation(ToModel, ToTable) {
|
pub fn relation(self: *Self) Relation(ToModel, ToTable) {
|
||||||
|
@ -122,6 +153,15 @@ pub fn typedMany(
|
||||||
.inlineMapping = inlineMapping,
|
.inlineMapping = inlineMapping,
|
||||||
.genJoin = genJoin,
|
.genJoin = genJoin,
|
||||||
.genSelect = genSelect,
|
.genSelect = genSelect,
|
||||||
|
.getQueryType = getQueryType,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn runtimeRelation(self: *Self) RuntimeRelation {
|
||||||
|
return .{
|
||||||
|
._interface = .{
|
||||||
|
.instance = self,
|
||||||
.buildQuery = buildQuery,
|
.buildQuery = buildQuery,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
@ -185,7 +225,9 @@ fn typedOne(
|
||||||
comptime config: OneConfiguration) type {
|
comptime config: OneConfiguration) type {
|
||||||
|
|
||||||
const FromKeyType = std.meta.fields(FromModel)[std.meta.fieldIndex(FromModel, fromRepositoryConfig.key[0]).?].type;
|
const FromKeyType = std.meta.fields(FromModel)[std.meta.fieldIndex(FromModel, fromRepositoryConfig.key[0]).?].type;
|
||||||
const QueryType = _query.RepositoryQuery(ToModel, ToTable, toRepositoryConfig, null);
|
const QueryType = _query.RepositoryQuery(ToModel, ToTable, toRepositoryConfig, null, struct {
|
||||||
|
__zrm_relation_key: FromKeyType,
|
||||||
|
});
|
||||||
|
|
||||||
// Get foreign key from relation config or repository config.
|
// Get foreign key from relation config or repository config.
|
||||||
const foreignKey = switch (config) {
|
const foreignKey = switch (config) {
|
||||||
|
@ -233,15 +275,29 @@ fn typedOne(
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
fn genSelect(_: *anyopaque, comptime table: []const u8, comptime prefix: []const u8) []const u8 {
|
fn _genSelect(comptime table: []const u8, comptime prefix: []const u8) []const u8 {
|
||||||
return _sql.SelectBuild(ToTable, table, prefix);
|
return _sql.SelectBuild(ToTable, table, prefix);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn buildQuery(_: *anyopaque, opaqueModels: []const anyopaque, opaqueQuery: *anyopaque) !void {
|
fn genSelect(_: *anyopaque, comptime table: []const u8, comptime prefix: []const u8) []const u8 {
|
||||||
var models: []const FromModel = undefined;
|
return _genSelect(table, prefix);
|
||||||
models.len = opaqueModels.len;
|
}
|
||||||
models.ptr = @ptrCast(@alignCast(opaqueModels.ptr));
|
|
||||||
const query: *QueryType = @ptrCast(@alignCast(opaqueQuery));
|
fn getQueryType() type {
|
||||||
|
return QueryType;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn buildQuery(_: *anyopaque, prefix: []const u8, opaqueModels: []const *anyopaque, allocator: std.mem.Allocator, connector: _database.Connector) !*anyopaque {
|
||||||
|
const models: []const *FromModel = @ptrCast(@alignCast(opaqueModels));
|
||||||
|
|
||||||
|
// Initialize the query to build.
|
||||||
|
const query: *QueryType = try allocator.create(QueryType);
|
||||||
|
errdefer allocator.destroy(query);
|
||||||
|
query.* = QueryType.init(allocator, connector, .{});
|
||||||
|
errdefer query.deinit();
|
||||||
|
|
||||||
|
// Build base SELECT.
|
||||||
|
const baseSelect = comptime _genSelect(toRepositoryConfig.table, "");
|
||||||
|
|
||||||
// Prepare given models IDs.
|
// Prepare given models IDs.
|
||||||
const modelsIds = try query.arena.allocator().alloc(FromKeyType, models.len);
|
const modelsIds = try query.arena.allocator().alloc(FromKeyType, models.len);
|
||||||
|
@ -251,8 +307,15 @@ fn typedOne(
|
||||||
|
|
||||||
switch (config) {
|
switch (config) {
|
||||||
.direct => {
|
.direct => {
|
||||||
|
// Add SELECT.
|
||||||
|
query.select(.{
|
||||||
|
.sql = baseSelect ++ ", \"" ++ fromRepositoryConfig.table ++ "\".\"" ++ fromRepositoryConfig.key[0] ++ "\" AS \"__zrm_relation_key\"",
|
||||||
|
.params = &[0]_sql.RawQueryParameter{},
|
||||||
|
});
|
||||||
|
|
||||||
query.join((_sql.RawQuery{
|
query.join((_sql.RawQuery{
|
||||||
.sql = "INNER JOIN \"" ++ fromRepositoryConfig.table ++ "\" ON \"" ++ toRepositoryConfig.table ++ "\"." ++ modelKey ++ " = \"" ++ fromRepositoryConfig.table ++ "\"." ++ foreignKey,
|
.sql = try std.fmt.allocPrint(query.arena.allocator(), "INNER JOIN \"" ++ fromRepositoryConfig.table ++ "\" AS \"{s}related" ++ "\" ON " ++
|
||||||
|
"\"" ++ toRepositoryConfig.table ++ "\"." ++ modelKey ++ " = \"{s}related" ++ "\"." ++ foreignKey, .{prefix, prefix}),
|
||||||
.params = &[0]_sql.RawQueryParameter{},
|
.params = &[0]_sql.RawQueryParameter{},
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
@ -260,20 +323,35 @@ fn typedOne(
|
||||||
try query.whereIn(FromKeyType, "\"" ++ fromRepositoryConfig.table ++ "\".\"" ++ fromRepositoryConfig.key[0] ++ "\"", modelsIds);
|
try query.whereIn(FromKeyType, "\"" ++ fromRepositoryConfig.table ++ "\".\"" ++ fromRepositoryConfig.key[0] ++ "\"", modelsIds);
|
||||||
},
|
},
|
||||||
.reverse => {
|
.reverse => {
|
||||||
// Build WHERE condition.
|
// Add SELECT.
|
||||||
try query.whereIn(FromKeyType, "\"" ++ toRepositoryConfig.table ++ "\".\"" ++ foreignKey ++ "\"", modelsIds);
|
query.select(.{
|
||||||
},
|
.sql = baseSelect ++ ", \"" ++ toRepositoryConfig.table ++ "\".\"" ++ foreignKey ++ "\" AS \"__zrm_relation_key\"",
|
||||||
.through => |through| {
|
|
||||||
query.join(.{
|
|
||||||
.sql = "INNER JOIN \"" ++ through.table ++ "\" ON " ++
|
|
||||||
"\"" ++ toRepositoryConfig.table ++ "\"." ++ modelKey ++ " = " ++ "\"" ++ through.table ++ "\"." ++ through.joinModelKey,
|
|
||||||
.params = &[0]_sql.RawQueryParameter{},
|
.params = &[0]_sql.RawQueryParameter{},
|
||||||
});
|
});
|
||||||
|
|
||||||
// Build WHERE condition.
|
// Build WHERE condition.
|
||||||
try query.whereIn(FromKeyType, "\"" ++ through.table ++ "\".\"" ++ through.joinForeignKey ++ "\"", modelsIds);
|
try query.whereIn(FromKeyType, "\"" ++ toRepositoryConfig.table ++ "\".\"" ++ foreignKey ++ "\"", modelsIds);
|
||||||
|
},
|
||||||
|
.through => |through| {
|
||||||
|
// Add SELECT.
|
||||||
|
query.select(.{
|
||||||
|
.sql = try std.fmt.allocPrint(query.arena.allocator(), baseSelect ++ ", \"{s}pivot" ++ "\".\"" ++ through.joinForeignKey ++ "\" AS \"__zrm_relation_key\"", .{prefix}),
|
||||||
|
.params = &[0]_sql.RawQueryParameter{},
|
||||||
|
});
|
||||||
|
|
||||||
|
query.join(.{
|
||||||
|
.sql = try std.fmt.allocPrint(query.arena.allocator(), "INNER JOIN \"" ++ through.table ++ "\" AS \"{s}pivot" ++ "\" ON " ++
|
||||||
|
"\"" ++ toRepositoryConfig.table ++ "\"." ++ modelKey ++ " = " ++ "\"{s}pivot" ++ "\"." ++ through.joinModelKey, .{prefix, prefix}),
|
||||||
|
.params = &[0]_sql.RawQueryParameter{},
|
||||||
|
});
|
||||||
|
|
||||||
|
// Build WHERE condition.
|
||||||
|
try query.whereIn(FromKeyType, try std.fmt.allocPrint(query.arena.allocator(), "\"{s}pivot" ++ "\".\"" ++ through.joinForeignKey ++ "\"", .{prefix}), modelsIds);
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Return built query.
|
||||||
|
return query;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn relation(self: *Self) Relation(ToModel, ToTable) {
|
pub fn relation(self: *Self) Relation(ToModel, ToTable) {
|
||||||
|
@ -285,6 +363,15 @@ fn typedOne(
|
||||||
.inlineMapping = inlineMapping,
|
.inlineMapping = inlineMapping,
|
||||||
.genJoin = genJoin,
|
.genJoin = genJoin,
|
||||||
.genSelect = genSelect,
|
.genSelect = genSelect,
|
||||||
|
.getQueryType = getQueryType,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn runtimeRelation(self: *Self) RuntimeRelation {
|
||||||
|
return .{
|
||||||
|
._interface = .{
|
||||||
|
.instance = self,
|
||||||
.buildQuery = buildQuery,
|
.buildQuery = buildQuery,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
@ -307,7 +394,7 @@ pub fn Relation(comptime ToModel: type, comptime ToTable: type) type {
|
||||||
inlineMapping: *const fn (self: *anyopaque) bool,
|
inlineMapping: *const fn (self: *anyopaque) bool,
|
||||||
genJoin: *const fn (self: *anyopaque, comptime alias: []const u8) []const u8,
|
genJoin: *const fn (self: *anyopaque, comptime alias: []const u8) []const u8,
|
||||||
genSelect: *const fn (self: *anyopaque, comptime table: []const u8, comptime prefix: []const u8) []const u8,
|
genSelect: *const fn (self: *anyopaque, comptime table: []const u8, comptime prefix: []const u8) []const u8,
|
||||||
buildQuery: *const fn (self: *anyopaque, models: []const anyopaque, query: *anyopaque) anyerror!void,
|
getQueryType: *const fn () type,
|
||||||
},
|
},
|
||||||
|
|
||||||
/// Read the related model repository configuration.
|
/// Read the related model repository configuration.
|
||||||
|
@ -331,14 +418,30 @@ pub fn Relation(comptime ToModel: type, comptime ToTable: type) type {
|
||||||
return self._interface.genSelect(self._interface.instance, table, prefix);
|
return self._interface.genSelect(self._interface.instance, table, prefix);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Build the query to retrieve relation data.
|
/// Get relation query type.
|
||||||
/// Is always used when inline mapping is not possible, but also when loading relations lazily.
|
pub fn getQueryType(self: Self) type {
|
||||||
pub fn buildQuery(self: Self, models: []const anyopaque, query: *anyopaque) !void {
|
return self._interface.getQueryType();
|
||||||
return self._interface.buildQuery(self._interface.instance, models, query);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Generic model runtime relation interface.
|
||||||
|
pub const RuntimeRelation = struct {
|
||||||
|
const Self = @This();
|
||||||
|
|
||||||
|
_interface: struct {
|
||||||
|
instance: *anyopaque,
|
||||||
|
|
||||||
|
buildQuery: *const fn (self: *anyopaque, prefix: []const u8, models: []const *anyopaque, allocator: std.mem.Allocator, connector: _database.Connector) anyerror!*anyopaque,
|
||||||
|
},
|
||||||
|
|
||||||
|
/// Build the query to retrieve relation data.
|
||||||
|
/// Is always used when inline mapping is not possible, but also when loading relations lazily.
|
||||||
|
pub fn buildQuery(self: Self, prefix: []const u8, models: []const *anyopaque, allocator: std.mem.Allocator, connector: _database.Connector) !*anyopaque {
|
||||||
|
return self._interface.buildQuery(self._interface.instance, prefix, models, allocator, connector);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
/// A model relation object.
|
/// A model relation object.
|
||||||
pub const ModelRelation = struct {
|
pub const ModelRelation = struct {
|
||||||
|
|
|
@ -110,7 +110,7 @@ pub fn Repository(comptime Model: type, comptime TableShape: type, comptime repo
|
||||||
pub const TableType = TableShape;
|
pub const TableType = TableShape;
|
||||||
pub const config = repositoryConfig;
|
pub const config = repositoryConfig;
|
||||||
|
|
||||||
pub const Query: type = query.RepositoryQuery(Model, TableShape, config, null);
|
pub const Query: type = query.RepositoryQuery(Model, TableShape, config, null, null);
|
||||||
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.
|
/// Type of one model key.
|
||||||
|
@ -153,7 +153,7 @@ pub fn Repository(comptime Model: type, comptime TableShape: type, comptime repo
|
||||||
};
|
};
|
||||||
|
|
||||||
pub fn QueryWith(comptime with: []const _relations.ModelRelation) type {
|
pub fn QueryWith(comptime with: []const _relations.ModelRelation) type {
|
||||||
return query.RepositoryQuery(Model, TableShape, config, with);
|
return query.RepositoryQuery(Model, TableShape, config, with, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn InsertCustom(comptime InsertShape: type) type {
|
pub fn InsertCustom(comptime InsertShape: type) type {
|
||||||
|
|
113
src/result.zig
113
src/result.zig
|
@ -1,20 +1,33 @@
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
const zollections = @import("zollections");
|
const zollections = @import("zollections");
|
||||||
|
const _database = @import("database.zig");
|
||||||
const _repository = @import("repository.zig");
|
const _repository = @import("repository.zig");
|
||||||
const _relations = @import("relations.zig");
|
const _relations = @import("relations.zig");
|
||||||
|
|
||||||
|
/// Structure of a model with its metadata.
|
||||||
|
pub fn ModelWithMetadata(comptime Model: type, comptime MetadataShape: ?type) type {
|
||||||
|
if (MetadataShape) |MetadataType| {
|
||||||
|
return struct {
|
||||||
|
model: Model,
|
||||||
|
metadata: MetadataType,
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
return Model;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Type of a retrieved table data, with its retrieved relations.
|
/// Type of a retrieved table data, with its retrieved relations.
|
||||||
pub fn TableWithRelations(comptime TableShape: type, comptime optionalRelations: ?[]const _relations.ModelRelation) type {
|
pub fn TableWithRelations(comptime TableShape: type, comptime MetadataShape: ?type, comptime optionalRelations: ?[]const _relations.ModelRelation) type {
|
||||||
if (optionalRelations) |relations| {
|
if (optionalRelations) |relations| {
|
||||||
const tableType = @typeInfo(TableShape);
|
const tableType = @typeInfo(TableShape);
|
||||||
|
|
||||||
// Build fields list: copy the existing table type fields and add those for relations.
|
// Build fields list: copy the existing table type fields and add those for relations.
|
||||||
var fields: [tableType.Struct.fields.len + relations.len]std.builtin.Type.StructField = undefined;
|
var fields: [tableType.Struct.fields.len + relations.len + (if (MetadataShape) |_| 1 else 0)]std.builtin.Type.StructField = undefined;
|
||||||
// Copy base table fields.
|
// Copy base table fields.
|
||||||
@memcpy(fields[0..tableType.Struct.fields.len], tableType.Struct.fields);
|
@memcpy(fields[0..tableType.Struct.fields.len], tableType.Struct.fields);
|
||||||
|
|
||||||
// For each relation, create a new struct field in the table shape.
|
// For each relation, create a new struct field in the table shape.
|
||||||
for (relations, fields[tableType.Struct.fields.len..]) |relation, *field| {
|
for (relations, fields[tableType.Struct.fields.len..(tableType.Struct.fields.len+relations.len)]) |relation, *field| {
|
||||||
// Get relation field type (optional TableShape of the related value).
|
// Get relation field type (optional TableShape of the related value).
|
||||||
comptime var relationImpl = relation.relation{};
|
comptime var relationImpl = relation.relation{};
|
||||||
const relationInstanceType = @TypeOf(relationImpl.relation());
|
const relationInstanceType = @TypeOf(relationImpl.relation());
|
||||||
|
@ -34,6 +47,17 @@ pub fn TableWithRelations(comptime TableShape: type, comptime optionalRelations:
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (MetadataShape) |MetadataType| {
|
||||||
|
// Add metadata field.
|
||||||
|
fields[tableType.Struct.fields.len + relations.len] = std.builtin.Type.StructField{
|
||||||
|
.name = "_zrm_metadata",
|
||||||
|
.type = MetadataType,
|
||||||
|
.default_value = null,
|
||||||
|
.is_comptime = false,
|
||||||
|
.alignment = @alignOf(MetadataType),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
// Build the new type.
|
// Build the new type.
|
||||||
return @Type(std.builtin.Type{
|
return @Type(std.builtin.Type{
|
||||||
.Struct = .{
|
.Struct = .{
|
||||||
|
@ -50,7 +74,7 @@ pub fn TableWithRelations(comptime TableShape: type, comptime optionalRelations:
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Convert a value of the fully retrieved type to the TableShape type.
|
/// Convert a value of the fully retrieved type to the TableShape type.
|
||||||
pub fn toTableShape(comptime TableShape: type, comptime optionalRelations: ?[]const _relations.ModelRelation, value: TableWithRelations(TableShape, optionalRelations)) TableShape {
|
pub fn toTableShape(comptime TableShape: type, comptime MetadataShape: ?type, comptime optionalRelations: ?[]const _relations.ModelRelation, value: TableWithRelations(TableShape, MetadataShape, optionalRelations)) TableShape {
|
||||||
if (optionalRelations) |_| {
|
if (optionalRelations) |_| {
|
||||||
// Make a structure of TableShape type.
|
// Make a structure of TableShape type.
|
||||||
var tableValue: TableShape = undefined;
|
var tableValue: TableShape = undefined;
|
||||||
|
@ -69,7 +93,7 @@ pub fn toTableShape(comptime TableShape: type, comptime optionalRelations: ?[]co
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Generic interface of a query result reader.
|
/// Generic interface of a query result reader.
|
||||||
pub fn QueryResultReader(comptime TableShape: type, comptime inlineRelations: ?[]const _relations.ModelRelation) type {
|
pub fn QueryResultReader(comptime TableShape: type, comptime MetadataShape: ?type, comptime inlineRelations: ?[]const _relations.ModelRelation) type {
|
||||||
return struct {
|
return struct {
|
||||||
const Self = @This();
|
const Self = @This();
|
||||||
|
|
||||||
|
@ -77,12 +101,12 @@ pub fn QueryResultReader(comptime TableShape: type, comptime inlineRelations: ?[
|
||||||
pub const Instance = struct {
|
pub const Instance = struct {
|
||||||
__interface: struct {
|
__interface: struct {
|
||||||
instance: *anyopaque,
|
instance: *anyopaque,
|
||||||
next: *const fn (self: *anyopaque) anyerror!?TableWithRelations(TableShape, inlineRelations),
|
next: *const fn (self: *anyopaque) anyerror!?TableWithRelations(TableShape, MetadataShape, inlineRelations),
|
||||||
},
|
},
|
||||||
|
|
||||||
allocator: std.mem.Allocator,
|
allocator: std.mem.Allocator,
|
||||||
|
|
||||||
pub fn next(self: Instance) !?TableWithRelations(TableShape, inlineRelations) {
|
pub fn next(self: Instance) !?TableWithRelations(TableShape, MetadataShape, inlineRelations) {
|
||||||
return self.__interface.next(self.__interface.instance);
|
return self.__interface.next(self.__interface.instance);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -100,11 +124,13 @@ pub fn QueryResultReader(comptime TableShape: type, comptime inlineRelations: ?[
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Map query result to repository model structures, and load the given relations.
|
/// Map query result to repository model structures, and load the given relations.
|
||||||
pub fn ResultMapper(comptime Model: type, comptime TableShape: type, comptime repositoryConfig: _repository.RepositoryConfiguration(Model, TableShape), comptime inlineRelations: ?[]const _relations.ModelRelation, comptime relations: ?[]const _relations.ModelRelation) type {
|
pub fn ResultMapper(comptime Model: type, comptime TableShape: type, comptime MetadataShape: ?type, comptime repositoryConfig: _repository.RepositoryConfiguration(Model, TableShape), comptime inlineRelations: ?[]const _relations.ModelRelation, comptime relations: ?[]const _relations.ModelRelation) type {
|
||||||
_ = relations;
|
|
||||||
return struct {
|
return struct {
|
||||||
/// Map the query result to a repository result, with all the required relations.
|
/// Map the query result to a repository result, with all the required relations.
|
||||||
pub fn map(allocator: std.mem.Allocator, queryReader: QueryResultReader(TableShape, inlineRelations)) !_repository.RepositoryResult(Model) {
|
pub fn map(comptime withMetadata: bool, allocator: std.mem.Allocator, connector: _database.Connector, queryReader: QueryResultReader(TableShape, MetadataShape, inlineRelations)) !_repository.RepositoryResult(if (withMetadata) ModelWithMetadata(Model, MetadataShape) else Model) {
|
||||||
|
// Get result type depending on metadata
|
||||||
|
const ResultType = if (withMetadata) ModelWithMetadata(Model, MetadataShape) else Model;
|
||||||
|
|
||||||
// Create an arena for mapper data.
|
// Create an arena for mapper data.
|
||||||
var mapperArena = std.heap.ArenaAllocator.init(allocator);
|
var mapperArena = std.heap.ArenaAllocator.init(allocator);
|
||||||
|
|
||||||
|
@ -112,14 +138,14 @@ pub fn ResultMapper(comptime Model: type, comptime TableShape: type, comptime re
|
||||||
const reader = try queryReader.init(mapperArena.allocator());
|
const reader = try queryReader.init(mapperArena.allocator());
|
||||||
|
|
||||||
// Initialize models list.
|
// Initialize models list.
|
||||||
var models = std.ArrayList(*Model).init(allocator);
|
var models = std.ArrayList(*ResultType).init(allocator);
|
||||||
defer models.deinit();
|
defer models.deinit();
|
||||||
|
|
||||||
// Get all raw models from the result reader.
|
// Get all raw models from the result reader.
|
||||||
while (try reader.next()) |rawModel| {
|
while (try reader.next()) |rawModel| {
|
||||||
// Parse each raw model from the reader.
|
// Parse each raw model from the reader.
|
||||||
const model = try allocator.create(Model);
|
const model = try allocator.create(ResultType);
|
||||||
model.* = try repositoryConfig.fromSql(toTableShape(TableShape, inlineRelations, rawModel));
|
(if (withMetadata) model.model else model.*) = try repositoryConfig.fromSql(toTableShape(TableShape, MetadataShape, inlineRelations, rawModel));
|
||||||
|
|
||||||
// Map inline relations.
|
// Map inline relations.
|
||||||
if (inlineRelations) |_inlineRelations| {
|
if (inlineRelations) |_inlineRelations| {
|
||||||
|
@ -136,14 +162,69 @@ pub fn ResultMapper(comptime Model: type, comptime TableShape: type, comptime re
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (withMetadata) {
|
||||||
|
// Set model metadata.
|
||||||
|
model.metadata = rawModel._zrm_metadata;
|
||||||
|
}
|
||||||
|
|
||||||
try models.append(model);
|
try models.append(model);
|
||||||
}
|
}
|
||||||
|
|
||||||
//TODO load relations?
|
if (relations) |relationsToLoad| {
|
||||||
|
inline for (relationsToLoad) |relation| {
|
||||||
|
const comptimeRelation = @constCast(&relation.relation{}).relation();
|
||||||
|
var relationImpl = relation.relation{};
|
||||||
|
const relationInstance = relationImpl.runtimeRelation();
|
||||||
|
|
||||||
|
// Build query for the relation to get.
|
||||||
|
const query: *comptimeRelation.getQueryType() = @ptrCast(@alignCast(
|
||||||
|
try relationInstance.buildQuery("relations." ++ relation.field ++ ".", @ptrCast(models.items), allocator, connector)
|
||||||
|
));
|
||||||
|
defer {
|
||||||
|
query.deinit();
|
||||||
|
allocator.destroy(query);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get related models.
|
||||||
|
const relatedModels = try query.getWithMetadata(mapperArena.allocator());
|
||||||
|
|
||||||
|
// Create a map with related models.
|
||||||
|
const RelatedModelsListType = std.ArrayList(@TypeOf(relatedModels.models[0].model));
|
||||||
|
const RelatedModelsMapType = std.AutoHashMap(std.meta.FieldType(@TypeOf(relatedModels.models[0].metadata), .__zrm_relation_key), RelatedModelsListType);
|
||||||
|
var relatedModelsMap = RelatedModelsMapType.init(allocator);
|
||||||
|
defer relatedModelsMap.deinit();
|
||||||
|
|
||||||
|
// Fill the map of related models, indexing them by the relation key.
|
||||||
|
for (relatedModels.models) |relatedModel| {
|
||||||
|
// For each related model, put it in the map at the relation key.
|
||||||
|
var modelsList = try relatedModelsMap.getOrPut(relatedModel.metadata.__zrm_relation_key);
|
||||||
|
|
||||||
|
if (!modelsList.found_existing) {
|
||||||
|
// Initialize the related models list.
|
||||||
|
modelsList.value_ptr.* = RelatedModelsListType.init(mapperArena.allocator());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add the current related model to the list.
|
||||||
|
try modelsList.value_ptr.append(relatedModel.model);
|
||||||
|
}
|
||||||
|
|
||||||
|
// For each model, at the grouped related models if there are some.
|
||||||
|
for (models.items) |model| {
|
||||||
|
@field(model, relation.field) = (
|
||||||
|
if (relatedModelsMap.getPtr(@field(model, repositoryConfig.key[0]))) |relatedModelsList|
|
||||||
|
// There are related models, set them.
|
||||||
|
try relatedModelsList.toOwnedSlice()
|
||||||
|
else
|
||||||
|
// No related models, set an empty array.
|
||||||
|
&[0](@TypeOf(relatedModels.models[0].model)){}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Return a result with the models.
|
// Return a result with the models.
|
||||||
return _repository.RepositoryResult(Model).init(allocator,
|
return _repository.RepositoryResult(ResultType).init(allocator,
|
||||||
zollections.Collection(Model).init(allocator, try models.toOwnedSlice()),
|
zollections.Collection(ResultType).init(allocator, try models.toOwnedSlice()),
|
||||||
mapperArena,
|
mapperArena,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -216,11 +216,11 @@ pub fn SelectBuilder(comptime TableShape: type) type {
|
||||||
/// Build a SELECT query part for a given table, renaming columns with the given prefix, at comptime.
|
/// Build a SELECT query part for a given table, renaming columns with the given prefix, at comptime.
|
||||||
pub fn SelectBuild(comptime TableShape: type, comptime table: []const u8, comptime prefix: []const u8) []const u8 {
|
pub fn SelectBuild(comptime TableShape: type, comptime table: []const u8, comptime prefix: []const u8) []const u8 {
|
||||||
// Initialize the selected columns string.
|
// Initialize the selected columns string.
|
||||||
var columnsSelect: []const u8 = "";
|
comptime var columnsSelect: []const u8 = "";
|
||||||
|
|
||||||
var first = true;
|
comptime var first = true;
|
||||||
// For each field, generate a format string.
|
// For each field, generate a format string.
|
||||||
for (@typeInfo(TableShape).Struct.fields) |field| {
|
inline for (@typeInfo(TableShape).Struct.fields) |field| {
|
||||||
// Add ", " between all selected columns (just not the first one).
|
// Add ", " between all selected columns (just not the first one).
|
||||||
if (first) {
|
if (first) {
|
||||||
first = false;
|
first = false;
|
||||||
|
|
|
@ -59,7 +59,7 @@ pub fn RepositoryUpdate(comptime Model: type, comptime TableShape: type, comptim
|
||||||
const Configuration = RepositoryUpdateConfiguration(UpdateShape);
|
const Configuration = RepositoryUpdateConfiguration(UpdateShape);
|
||||||
|
|
||||||
/// Result mapper type.
|
/// Result mapper type.
|
||||||
pub const ResultMapper = _result.ResultMapper(Model, TableShape, repositoryConfig, null, null);
|
pub const ResultMapper = _result.ResultMapper(Model, TableShape, null, repositoryConfig, null, null);
|
||||||
|
|
||||||
arena: std.heap.ArenaAllocator,
|
arena: std.heap.ArenaAllocator,
|
||||||
connector: database.Connector,
|
connector: database.Connector,
|
||||||
|
@ -305,8 +305,8 @@ pub fn RepositoryUpdate(comptime Model: type, comptime TableShape: type, comptim
|
||||||
defer queryResult.deinit();
|
defer queryResult.deinit();
|
||||||
|
|
||||||
// Map query results.
|
// Map query results.
|
||||||
var postgresqlReader = postgresql.QueryResultReader(TableShape, null).init(queryResult);
|
var postgresqlReader = postgresql.QueryResultReader(TableShape, null, null).init(queryResult);
|
||||||
return try ResultMapper.map(allocator, postgresqlReader.reader());
|
return try ResultMapper.map(false, allocator, self.connector, postgresqlReader.reader());
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Initialize a new repository update query.
|
/// Initialize a new repository update query.
|
||||||
|
|
|
@ -18,7 +18,7 @@ fn initDatabase(allocator: std.mem.Allocator) !void {
|
||||||
.password = "zrm",
|
.password = "zrm",
|
||||||
.database = "zrm",
|
.database = "zrm",
|
||||||
},
|
},
|
||||||
.size = 1,
|
.size = 5,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -49,7 +49,53 @@ test "belongsTo" {
|
||||||
try std.testing.expectEqual(1, result.models[0].parent_id);
|
try std.testing.expectEqual(1, result.models[0].parent_id);
|
||||||
try std.testing.expectEqual(1, result.models[1].parent_id);
|
try std.testing.expectEqual(1, result.models[1].parent_id);
|
||||||
try std.testing.expectEqual(repository.MyModel, @TypeOf(result.models[0].parent.?));
|
try std.testing.expectEqual(repository.MyModel, @TypeOf(result.models[0].parent.?));
|
||||||
try std.testing.expectEqual(repository.MyModel, @TypeOf(result.models[1].parent.?));
|
|
||||||
try std.testing.expectEqual(1, result.models[0].parent.?.id);
|
try std.testing.expectEqual(1, result.models[0].parent.?.id);
|
||||||
try std.testing.expectEqual(1, result.models[1].parent.?.id);
|
try std.testing.expectEqual(1, result.models[1].parent.?.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
test "hasMany" {
|
||||||
|
zrm.setDebug(true);
|
||||||
|
|
||||||
|
try initDatabase(std.testing.allocator);
|
||||||
|
defer database.deinit();
|
||||||
|
var poolConnector = zrm.database.PoolConnector{
|
||||||
|
.pool = database,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Build a query of submodels.
|
||||||
|
var myQuery = repository.MyModelRepository.QueryWith(
|
||||||
|
// Retrieve parents of submodels from relation.
|
||||||
|
&[_]zrm.relations.ModelRelation{repository.MyModelRelations.submodels}
|
||||||
|
).init(std.testing.allocator, poolConnector.connector(), .{});
|
||||||
|
defer myQuery.deinit();
|
||||||
|
|
||||||
|
try myQuery.buildSql();
|
||||||
|
|
||||||
|
// Get query result.
|
||||||
|
var result = try myQuery.get(std.testing.allocator);
|
||||||
|
defer result.deinit();
|
||||||
|
|
||||||
|
// Checking result.
|
||||||
|
try std.testing.expectEqual(4, result.models.len);
|
||||||
|
try std.testing.expectEqual(repository.MySubmodel, @TypeOf(result.models[0].submodels.?[0]));
|
||||||
|
|
||||||
|
// Checking retrieved submodels.
|
||||||
|
for (result.models) |model| {
|
||||||
|
try std.testing.expect(model.submodels != null);
|
||||||
|
|
||||||
|
if (model.submodels.?.len > 0) {
|
||||||
|
try std.testing.expectEqual(1, model.id);
|
||||||
|
try std.testing.expectEqual(2, model.submodels.?.len);
|
||||||
|
for (model.submodels.?) |submodel| {
|
||||||
|
try std.testing.expectEqual(1, submodel.parent_id.?);
|
||||||
|
try std.testing.expect(
|
||||||
|
std.mem.eql(u8, &try pg.uuidToHex(submodel.uuid), "f6868a5b-2efc-455f-b76e-872df514404f")
|
||||||
|
or std.mem.eql(u8, &try pg.uuidToHex(submodel.uuid), "013ef171-9781-40e9-b843-f6bc11890070")
|
||||||
|
);
|
||||||
|
try std.testing.expect(
|
||||||
|
std.mem.eql(u8, submodel.label, "test") or std.mem.eql(u8, submodel.label, "another")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -22,7 +22,7 @@ fn initDatabase() !void {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// An example submodel, child of the example model.
|
/// An example submodel, child of the example model.
|
||||||
const MySubmodel = struct {
|
pub const MySubmodel = struct {
|
||||||
uuid: []const u8,
|
uuid: []const u8,
|
||||||
label: []const u8,
|
label: []const u8,
|
||||||
|
|
||||||
|
|
Loading…
Add table
Reference in a new issue