Easier access to relations types and comptime values and improved definition process.

This commit is contained in:
Madeorsk 2024-11-25 18:41:29 +01:00
parent ce5aeffd2c
commit 97c4c429e1
Signed by: Madeorsk
GPG key ID: 677E51CA765BB79F
6 changed files with 325 additions and 357 deletions

View file

@ -162,7 +162,7 @@ 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 MetadataShape: ?type, comptime inlineRelations: ?[]const _relations.ModelRelation) type { pub fn QueryResultReader(comptime TableShape: type, comptime MetadataShape: ?type, comptime inlineRelations: ?[]const _relations.Relation) type {
const InstanceInterface = _result.QueryResultReader(TableShape, MetadataShape, inlineRelations).Instance; const InstanceInterface = _result.QueryResultReader(TableShape, MetadataShape, inlineRelations).Instance;
// Build relations mappers container type. // Build relations mappers container type.
@ -173,9 +173,7 @@ pub fn QueryResultReader(comptime TableShape: type, comptime MetadataShape: ?typ
for (_inlineRelations, &fields) |relation, *field| { for (_inlineRelations, &fields) |relation, *field| {
// Get relation field type (TableShape of the related value). // Get relation field type (TableShape of the related value).
var relationImpl = relation.relation{}; const relationFieldType = PgMapper(relation.TableShape);
const relationInstanceType = @TypeOf(relationImpl.relation());
const relationFieldType = PgMapper(relationInstanceType.TableShape);
field.* = .{ field.* = .{
.name = relation.field ++ [0:0]u8{}, .name = relation.field ++ [0:0]u8{},
@ -278,11 +276,8 @@ pub fn QueryResultReader(comptime TableShape: type, comptime MetadataShape: ?typ
if (inlineRelations) |_inlineRelations| { if (inlineRelations) |_inlineRelations| {
// Initialize mapper for each relation. // Initialize mapper for each relation.
inline for (_inlineRelations) |relation| { inline for (_inlineRelations) |relation| {
// Get relation field type (TableShape of the related value).
comptime var relationImpl = relation.relation{};
const relationInstanceType = @TypeOf(relationImpl.relation());
@field(self.instance.relationsMappers, relation.field) = @field(self.instance.relationsMappers, relation.field) =
try makeMapper(relationInstanceType.TableShape, self.result, allocator, "relations." ++ relation.field ++ "."); try makeMapper(relation.TableShape, self.result, allocator, "relations." ++ relation.field ++ ".");
} }
} }

View file

@ -20,20 +20,20 @@ pub const RepositoryQueryConfiguration = struct {
/// Compiled relations structure. /// Compiled relations structure.
const CompiledRelations = struct { const CompiledRelations = struct {
inlineRelations: []relations.ModelRelation, inlineRelations: []relations.Relation,
otherRelations: []relations.ModelRelation, otherRelations: []relations.Relation,
inlineSelect: []const u8, inlineSelect: []const u8,
inlineJoins: []const u8, inlineJoins: []const u8,
}; };
/// 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, comptime MetadataShape: ?type) type { pub fn RepositoryQuery(comptime Model: type, comptime TableShape: type, comptime repositoryConfig: repository.RepositoryConfiguration(Model, TableShape), comptime with: ?[]const relations.Relation, 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.Relation = &[0]relations.Relation{};
// Other relations list. // Other relations list.
var otherRelations: []relations.ModelRelation = &[0]relations.ModelRelation{}; var otherRelations: []relations.Relation = &[0]relations.Relation{};
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.
@ -45,20 +45,14 @@ pub fn RepositoryQuery(comptime Model: type, comptime TableShape: type, comptime
for (_with) |relation| { for (_with) |relation| {
// For each relation, determine if it's inline or not. // For each relation, determine if it's inline or not.
var relationImpl = relation.relation{}; if (relation.inlineMapping) {
const relationInstance = relationImpl.relation();
if (relationInstance.inlineMapping()) {
// Add the current relation to inline relations. // Add the current relation to inline relations.
inlineRelations = @ptrCast(@constCast(_comptime.append(inlineRelations, relation))); inlineRelations = @ptrCast(@constCast(_comptime.append(inlineRelations, relation)));
// Build table alias and fields prefix for the relation.
const tableAlias = "relations." ++ relation.field;
const fieldsPrefix = tableAlias ++ ".";
// Generate selected columns for the relation. // Generate selected columns for the relation.
inlineSelect = @ptrCast(@constCast(_comptime.append(inlineSelect, relationInstance.genSelect(tableAlias, fieldsPrefix)))); inlineSelect = @ptrCast(@constCast(_comptime.append(inlineSelect, relation.select)));
// 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, relation.join)));
} else { } else {
// Add the current relation to other relations. // Add the current relation to other relations.
otherRelations = @ptrCast(@constCast(_comptime.append(otherRelations, relation))); otherRelations = @ptrCast(@constCast(_comptime.append(otherRelations, relation)));
@ -73,8 +67,8 @@ pub fn RepositoryQuery(comptime Model: type, comptime TableShape: type, comptime
}; };
} else { } else {
break :compile CompiledRelations{ break :compile CompiledRelations{
.inlineRelations = &[0]relations.ModelRelation{}, .inlineRelations = &[0]relations.Relation{},
.otherRelations = &[0]relations.ModelRelation{}, .otherRelations = &[0]relations.Relation{},
.inlineSelect = "", .inlineSelect = "",
.inlineJoins = "", .inlineJoins = "",
}; };

View file

@ -50,6 +50,9 @@ pub fn typedMany(
comptime toRepositoryConfig: repository.RepositoryConfiguration(ToModel, ToTable), comptime toRepositoryConfig: repository.RepositoryConfiguration(ToModel, ToTable),
comptime config: ManyConfiguration) type { comptime config: ManyConfiguration) type {
return struct {
/// Relation implementation.
pub fn Implementation(field: []const u8) type {
// 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) {
.direct => |direct| direct.foreignKey, .direct => |direct| direct.foreignKey,
@ -67,30 +70,17 @@ pub fn typedMany(
__zrm_relation_key: FromKeyType, __zrm_relation_key: FromKeyType,
}); });
const alias = "relations." ++ field;
const prefix = alias ++ ".";
return struct { return struct {
const Self = @This(); const Self = @This();
fn getRepositoryConfiguration(_: *anyopaque) repository.RepositoryConfiguration(ToModel, ToTable) { fn genSelect() []const u8 {
return toRepositoryConfig; return _sql.SelectBuild(ToTable, alias, prefix);
} }
fn inlineMapping(_: *anyopaque) bool { fn buildQuery(opaqueModels: []const *anyopaque, allocator: std.mem.Allocator, connector: _database.Connector) !*anyopaque {
return false;
}
fn genJoin(_: *anyopaque, comptime _: []const u8) []const u8 {
unreachable; // No possible join in a many relation.
}
fn _genSelect(comptime table: []const u8, comptime prefix: []const u8) []const u8 {
return _sql.SelectBuild(ToTable, table, prefix);
}
fn genSelect(_: *anyopaque, comptime table: []const u8, comptime prefix: []const u8) []const u8 {
return _genSelect(table, prefix);
}
fn buildQuery(_: *anyopaque, prefix: []const u8, opaqueModels: []const *anyopaque, allocator: std.mem.Allocator, connector: _database.Connector) !*anyopaque {
const models: []const *FromModel = @ptrCast(@alignCast(opaqueModels)); const models: []const *FromModel = @ptrCast(@alignCast(opaqueModels));
// Initialize the query to build. // Initialize the query to build.
@ -100,7 +90,7 @@ pub fn typedMany(
errdefer query.deinit(); errdefer query.deinit();
// Build base SELECT. // Build base SELECT.
const baseSelect = comptime _genSelect(toRepositoryConfig.table, ""); const baseSelect = comptime _sql.SelectBuild(ToTable, 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);
@ -140,26 +130,26 @@ pub fn typedMany(
return query; // Return built query. return query; // Return built query.
} }
pub fn relation(self: *Self) Relation(ToModel, ToTable) { /// Build the "many" generic relation.
pub fn relation(_: Self) Relation {
return .{ return .{
._interface = .{ ._interface = .{
.instance = self, .repositoryConfiguration = &toRepositoryConfig,
.getRepositoryConfiguration = getRepositoryConfiguration,
.inlineMapping = inlineMapping,
.genJoin = genJoin,
.genSelect = genSelect,
},
.QueryType = QueryType,
};
}
pub fn runtimeRelation(self: *Self) RuntimeRelation {
return .{
._interface = .{
.instance = self,
.buildQuery = buildQuery, .buildQuery = buildQuery,
}, },
.Model = ToModel,
.TableShape = ToTable,
.field = field,
.alias = alias,
.prefix = prefix,
.QueryType = QueryType,
.inlineMapping = false,
.join = undefined,
.select = genSelect(),
};
}
}; };
} }
}; };
@ -220,6 +210,8 @@ fn typedOne(
comptime toRepositoryConfig: repository.RepositoryConfiguration(ToModel, ToTable), comptime toRepositoryConfig: repository.RepositoryConfiguration(ToModel, ToTable),
comptime config: OneConfiguration) type { comptime config: OneConfiguration) type {
return struct {
pub fn Implementation(field: []const u8) 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, struct { const QueryType = _query.RepositoryQuery(ToModel, ToTable, toRepositoryConfig, null, struct {
__zrm_relation_key: FromKeyType, __zrm_relation_key: FromKeyType,
@ -239,18 +231,13 @@ fn typedOne(
.through => |through| if (through.modelKey) |_modelKey| _modelKey else toRepositoryConfig.key[0], .through => |through| if (through.modelKey) |_modelKey| _modelKey else toRepositoryConfig.key[0],
}; };
const alias = "relations." ++ field;
const prefix = alias ++ ".";
return struct { return struct {
const Self = @This(); const Self = @This();
fn getRepositoryConfiguration(_: *anyopaque) repository.RepositoryConfiguration(ToModel, ToTable) { fn genJoin() []const u8 {
return toRepositoryConfig;
}
fn inlineMapping(_: *anyopaque) bool {
return true;
}
fn genJoin(_: *anyopaque, comptime alias: []const u8) []const u8 {
return switch (config) { return switch (config) {
.direct => ( .direct => (
"LEFT JOIN \"" ++ toRepositoryConfig.table ++ "\" AS \"" ++ alias ++ "\" ON " ++ "LEFT JOIN \"" ++ toRepositoryConfig.table ++ "\" AS \"" ++ alias ++ "\" ON " ++
@ -271,15 +258,11 @@ fn typedOne(
}; };
} }
fn _genSelect(comptime table: []const u8, comptime prefix: []const u8) []const u8 { fn genSelect() []const u8 {
return _sql.SelectBuild(ToTable, table, prefix); return _sql.SelectBuild(ToTable, alias, prefix);
} }
fn genSelect(_: *anyopaque, comptime table: []const u8, comptime prefix: []const u8) []const u8 { fn buildQuery(opaqueModels: []const *anyopaque, allocator: std.mem.Allocator, connector: _database.Connector) !*anyopaque {
return _genSelect(table, prefix);
}
fn buildQuery(_: *anyopaque, prefix: []const u8, opaqueModels: []const *anyopaque, allocator: std.mem.Allocator, connector: _database.Connector) !*anyopaque {
const models: []const *FromModel = @ptrCast(@alignCast(opaqueModels)); const models: []const *FromModel = @ptrCast(@alignCast(opaqueModels));
// Initialize the query to build. // Initialize the query to build.
@ -289,7 +272,7 @@ fn typedOne(
errdefer query.deinit(); errdefer query.deinit();
// Build base SELECT. // Build base SELECT.
const baseSelect = comptime _genSelect(toRepositoryConfig.table, ""); const baseSelect = comptime _sql.SelectBuild(ToTable, 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);
@ -346,102 +329,79 @@ fn typedOne(
return query; return query;
} }
pub fn relation(self: *Self) Relation(ToModel, ToTable) { /// Build the "one" generic relation.
pub fn relation(_: Self) Relation {
return .{ return .{
._interface = .{ ._interface = .{
.instance = self, .repositoryConfiguration = &toRepositoryConfig,
.getRepositoryConfiguration = getRepositoryConfiguration,
.inlineMapping = inlineMapping,
.genJoin = genJoin,
.genSelect = genSelect,
},
.QueryType = QueryType,
};
}
pub fn runtimeRelation(self: *Self) RuntimeRelation {
return .{
._interface = .{
.instance = self,
.buildQuery = buildQuery, .buildQuery = buildQuery,
}, },
.Model = ToModel,
.TableShape = ToTable,
.field = field,
.alias = alias,
.prefix = prefix,
.QueryType = QueryType,
.inlineMapping = true,
.join = genJoin(),
.select = genSelect(),
};
}
}; };
} }
}; };
} }
/// Generic model relation interface. /// Generic model relation interface.
pub fn Relation(comptime ToModel: type, comptime ToTable: type) type { pub const Relation = struct {
return struct {
const Self = @This(); const Self = @This();
pub const Model = ToModel;
pub const TableShape = ToTable;
_interface: struct { _interface: struct {
instance: *anyopaque, repositoryConfiguration: *const anyopaque,
getRepositoryConfiguration: *const fn (self: *anyopaque) repository.RepositoryConfiguration(ToModel, ToTable), buildQuery: *const fn (models: []const *anyopaque, allocator: std.mem.Allocator, connector: _database.Connector) anyerror!*anyopaque,
inlineMapping: *const fn (self: *anyopaque) bool,
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,
}, },
/// Type of the related model.
Model: type,
/// Type of the related model table.
TableShape: type,
/// Field where to put the related model(s).
field: []const u8,
/// Table alias of the relation.
alias: []const u8,
/// Prefix of fields of the relation.
prefix: []const u8,
/// Type of a query of the related models.
QueryType: type, QueryType: type,
/// Read the related model repository configuration. /// Set if relation mapping is done inline: this means that it's done at the same time the model is mapped,
pub fn getRepositoryConfiguration(self: Self) repository.RepositoryConfiguration(ToModel, ToTable) {
return self._interface.getRepositoryConfiguration(self._interface.instance);
}
/// Relation mapping is done inline: this means that it's done at the same time the model is mapped,
/// and that the associated data will be retrieved in the main query. /// and that the associated data will be retrieved in the main query.
pub fn inlineMapping(self: Self) bool { inlineMapping: bool,
return self._interface.inlineMapping(self._interface.instance); /// In case of inline mapping, the JOIN clause to retrieve the associated data.
} join: []const u8,
/// The SELECT clause to retrieve the associated data.
/// In case of inline mapping, generate a JOIN clause to retrieve the associated data. select: []const u8,
pub fn genJoin(self: Self, comptime alias: []const u8) []const u8 {
return self._interface.genJoin(self._interface.instance, alias);
}
/// Generate a SELECT clause to retrieve the associated data, with the given table and prefix.
pub fn genSelect(self: Self, comptime table: []const u8, comptime prefix: []const u8) []const u8 {
return self._interface.genSelect(self._interface.instance, table, prefix);
}
};
}
/// 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. /// Build the query to retrieve relation data.
/// Is always used when inline mapping is not possible, but also when loading relations lazily. /// 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 { pub fn buildQuery(self: Self, models: []const *anyopaque, allocator: std.mem.Allocator, connector: _database.Connector) !*anyopaque {
return self._interface.buildQuery(self._interface.instance, prefix, models, allocator, connector); return self._interface.buildQuery(models, allocator, connector);
} }
};
/// Get typed repository configuration for the related model.
/// A model relation object. pub fn repositoryConfiguration(self: Self) repository.RepositoryConfiguration(self.Model, self.TableShape) {
pub const ModelRelation = struct { const repoConfig: *const repository.RepositoryConfiguration(self.Model, self.TableShape)
relation: type, = @ptrCast(@alignCast(self._interface.repositoryConfiguration));
field: []const u8, return repoConfig.*;
}
}; };
/// Structure of an eager loaded relation. /// Structure of an eager loaded relation.
pub const Eager = struct { pub const Eager = struct {
/// Model field to fill for the relation.
field: []const u8,
/// The relation to eager load. /// The relation to eager load.
relation: Relation, relation: Relation,
/// Subrelations to eager load. /// Subrelations to eager load.

View file

@ -78,18 +78,46 @@ pub fn ModelKeyType(comptime Model: type, comptime TableShape: type, comptime co
pub fn RelationsDefinitionType(comptime rawDefinition: anytype) type { pub fn RelationsDefinitionType(comptime rawDefinition: anytype) type {
const rawDefinitionType = @typeInfo(@TypeOf(rawDefinition)); const rawDefinitionType = @typeInfo(@TypeOf(rawDefinition));
// Build model relations fields. // Build relations fields and implementations fields.
var fields: [rawDefinitionType.Struct.fields.len]std.builtin.Type.StructField = undefined; var fields: [1 + rawDefinitionType.Struct.fields.len]std.builtin.Type.StructField = undefined;
inline for (rawDefinitionType.Struct.fields, &fields) |originalField, *field| { var implementationsFields: [rawDefinitionType.Struct.fields.len]std.builtin.Type.StructField = undefined;
field.* = .{
inline for (rawDefinitionType.Struct.fields, fields[1..], &implementationsFields) |originalField, *field, *implementationField| {
field.* = std.builtin.Type.StructField{
.name = originalField.name, .name = originalField.name,
.type = _relations.ModelRelation, .type = _relations.Relation,
.default_value = null, .default_value = null,
.is_comptime = false, .is_comptime = false,
.alignment = @alignOf(_relations.ModelRelation), .alignment = @alignOf(type),
};
const ImplementationType = @field(rawDefinition, originalField.name).Implementation(originalField.name);
implementationField.* = std.builtin.Type.StructField{
.name = originalField.name,
.type = ImplementationType,
.default_value = &ImplementationType{},
.is_comptime = false,
.alignment = @alignOf(ImplementationType),
}; };
} }
// Add implementations field.
const ImplementationsType = @Type(.{
.Struct = std.builtin.Type.Struct{
.layout = std.builtin.Type.ContainerLayout.auto,
.fields = &implementationsFields,
.decls = &[_]std.builtin.Type.Declaration{},
.is_tuple = false,
},
});
fields[0] = std.builtin.Type.StructField{
.name = "_implementations",
.type = ImplementationsType,
.default_value = null,
.is_comptime = false,
.alignment = @alignOf(ImplementationsType),
};
// Return built type. // Return built type.
return @Type(.{ return @Type(.{
.Struct = std.builtin.Type.Struct{ .Struct = std.builtin.Type.Struct{
@ -134,17 +162,16 @@ pub fn Repository(comptime Model: type, comptime TableShape: type, comptime repo
// Initialize final relations definition. // Initialize final relations definition.
var definition: RelationsDefinitionType(rawDefinition) = undefined; var definition: RelationsDefinitionType(rawDefinition) = undefined;
definition._implementations = .{};
// Check that the definition structure only include known fields. // Check that the definition structure only include known fields.
inline for (std.meta.fieldNames(rawDefinitionType)) |fieldName| { inline for (std.meta.fieldNames(rawDefinitionType)) |fieldName| {
if (!@hasField(Model, fieldName)) { if (!@hasField(Model, fieldName)) {
@compileError("No corresponding field for relation " ++ fieldName); @compileError("No corresponding field for relation " ++ fieldName);
} }
// Alter definition structure to add the field name. // Alter definition structure to set the relation instance.
@field(definition, fieldName) = .{ @field(definition, fieldName) = @field(definition._implementations, fieldName).relation();
.relation = @field(rawDefinition, fieldName),
.field = fieldName,
};
} }
// Return altered definition structure. // Return altered definition structure.
@ -152,7 +179,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.Relation) type {
return query.RepositoryQuery(Model, TableShape, config, with, null); return query.RepositoryQuery(Model, TableShape, config, with, null);
} }

View file

@ -17,7 +17,7 @@ pub fn ModelWithMetadata(comptime Model: type, comptime MetadataShape: ?type) ty
} }
/// 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 MetadataShape: ?type, comptime optionalRelations: ?[]const _relations.ModelRelation) type { pub fn TableWithRelations(comptime TableShape: type, comptime MetadataShape: ?type, comptime optionalRelations: ?[]const _relations.Relation) type {
if (optionalRelations) |relations| { if (optionalRelations) |relations| {
const tableType = @typeInfo(TableShape); const tableType = @typeInfo(TableShape);
@ -29,11 +29,9 @@ pub fn TableWithRelations(comptime TableShape: type, comptime MetadataShape: ?ty
// 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..(tableType.Struct.fields.len+relations.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{};
const relationInstanceType = @TypeOf(relationImpl.relation());
const relationFieldType = @Type(std.builtin.Type{ const relationFieldType = @Type(std.builtin.Type{
.Optional = .{ .Optional = .{
.child = relationInstanceType.TableShape .child = relation.TableShape
}, },
}); });
@ -74,7 +72,7 @@ pub fn TableWithRelations(comptime TableShape: type, comptime MetadataShape: ?ty
} }
/// 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 MetadataShape: ?type, comptime optionalRelations: ?[]const _relations.ModelRelation, value: TableWithRelations(TableShape, MetadataShape, optionalRelations)) TableShape { pub fn toTableShape(comptime TableShape: type, comptime MetadataShape: ?type, comptime optionalRelations: ?[]const _relations.Relation, 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;
@ -93,7 +91,7 @@ pub fn toTableShape(comptime TableShape: type, comptime MetadataShape: ?type, co
} }
/// Generic interface of a query result reader. /// Generic interface of a query result reader.
pub fn QueryResultReader(comptime TableShape: type, comptime MetadataShape: ?type, comptime inlineRelations: ?[]const _relations.ModelRelation) type { pub fn QueryResultReader(comptime TableShape: type, comptime MetadataShape: ?type, comptime inlineRelations: ?[]const _relations.Relation) type {
return struct { return struct {
const Self = @This(); const Self = @This();
@ -124,7 +122,7 @@ pub fn QueryResultReader(comptime TableShape: type, comptime MetadataShape: ?typ
} }
/// 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 MetadataShape: ?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.Relation, comptime relations: ?[]const _relations.Relation) type {
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(comptime withMetadata: bool, allocator: std.mem.Allocator, connector: _database.Connector, queryReader: QueryResultReader(TableShape, MetadataShape, inlineRelations)) !_repository.RepositoryResult(if (withMetadata) ModelWithMetadata(Model, MetadataShape) else 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) {
@ -151,12 +149,10 @@ pub fn ResultMapper(comptime Model: type, comptime TableShape: type, comptime Me
if (inlineRelations) |_inlineRelations| { if (inlineRelations) |_inlineRelations| {
// If there are loaded inline relations, map them to the result. // If there are loaded inline relations, map them to the result.
inline for (_inlineRelations) |relation| { inline for (_inlineRelations) |relation| {
comptime var relationImpl = relation.relation{};
const relationInstance = relationImpl.relation();
// Set the read inline relation value. // Set the read inline relation value.
@field(model.*, relation.field) = ( @field(model.*, relation.field) = (
if (@field(rawModel, relation.field)) |relationVal| if (@field(rawModel, relation.field)) |relationVal|
try relationInstance.getRepositoryConfiguration().fromSql(relationVal) try relation.repositoryConfiguration().fromSql(relationVal)
else null else null
); );
} }
@ -172,13 +168,9 @@ pub fn ResultMapper(comptime Model: type, comptime TableShape: type, comptime Me
if (relations) |relationsToLoad| { if (relations) |relationsToLoad| {
inline for (relationsToLoad) |relation| { 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. // Build query for the relation to get.
const query: *comptimeRelation.QueryType = @ptrCast(@alignCast( const query: *relation.QueryType = @ptrCast(@alignCast(
try relationInstance.buildQuery("relations." ++ relation.field ++ ".", @ptrCast(models.items), allocator, connector) try relation.buildQuery(@ptrCast(models.items), allocator, connector)
)); ));
defer { defer {
query.deinit(); query.deinit();

View file

@ -34,7 +34,7 @@ test "belongsTo" {
// Build a query of submodels. // Build a query of submodels.
var myQuery = repository.MySubmodelRepository.QueryWith( var myQuery = repository.MySubmodelRepository.QueryWith(
// Retrieve parents of submodels from relation. // Retrieve parents of submodels from relation.
&[_]zrm.relations.ModelRelation{repository.MySubmodelRelations.parent} &[_]zrm.relations.Relation{repository.MySubmodelRelations.parent}
).init(std.testing.allocator, poolConnector.connector(), .{}); ).init(std.testing.allocator, poolConnector.connector(), .{});
defer myQuery.deinit(); defer myQuery.deinit();
@ -65,7 +65,7 @@ test "hasMany" {
// Build a query of submodels. // Build a query of submodels.
var myQuery = repository.MyModelRepository.QueryWith( var myQuery = repository.MyModelRepository.QueryWith(
// Retrieve parents of submodels from relation. // Retrieve parents of submodels from relation.
&[_]zrm.relations.ModelRelation{repository.MyModelRelations.submodels} &[_]zrm.relations.Relation{repository.MyModelRelations.submodels}
).init(std.testing.allocator, poolConnector.connector(), .{}); ).init(std.testing.allocator, poolConnector.connector(), .{});
defer myQuery.deinit(); defer myQuery.deinit();