Handle inline relations in result parsing.
+ Parse inline relations when parsing a query result. + Implement a custom pg.Mapper to pass the row manually.
This commit is contained in:
		
							parent
							
								
									e33bc5aaa2
								
							
						
					
					
						commit
						24989603a3
					
				
					 4 changed files with 305 additions and 42 deletions
				
			
		| 
						 | 
					@ -59,8 +59,89 @@ pub fn handleRawPostgresqlError(err: anyerror, connection: *pg.Conn) anyerror {
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					fn isSlice(comptime T: type) ?type {
 | 
				
			||||||
 | 
						switch(@typeInfo(T)) {
 | 
				
			||||||
 | 
							.Pointer => |ptr| {
 | 
				
			||||||
 | 
								if (ptr.size != .Slice) {
 | 
				
			||||||
 | 
									@compileError("cannot get value of type " ++ @typeName(T));
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								return if (ptr.child == u8) null else ptr.child;
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							.Optional => |opt| return isSlice(opt.child),
 | 
				
			||||||
 | 
							else => return null,
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					fn mapValue(comptime T: type, value: T, allocator: std.mem.Allocator) !T {
 | 
				
			||||||
 | 
						switch (@typeInfo(T)) {
 | 
				
			||||||
 | 
							.Optional => |opt| {
 | 
				
			||||||
 | 
								if (value) |v| {
 | 
				
			||||||
 | 
									return try mapValue(opt.child, v, allocator);
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								return null;
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							else => {},
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (T == []u8 or T == []const u8) {
 | 
				
			||||||
 | 
							return try allocator.dupe(u8, value);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (std.meta.hasFn(T, "pgzMoveOwner")) {
 | 
				
			||||||
 | 
							return value.pgzMoveOwner(allocator);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return value;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					fn rowMapColumn(self: *const pg.Row, field: *const std.builtin.Type.StructField, optional_column_index: ?usize, allocator: ?std.mem.Allocator) !field.type {
 | 
				
			||||||
 | 
						const T = field.type;
 | 
				
			||||||
 | 
						const column_index = optional_column_index orelse {
 | 
				
			||||||
 | 
							if (field.default_value) |dflt| {
 | 
				
			||||||
 | 
								return @as(*align(1) const field.type, @ptrCast(dflt)).*;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							return error.FieldColumnMismatch;
 | 
				
			||||||
 | 
						};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (comptime isSlice(T)) |S| {
 | 
				
			||||||
 | 
							const slice = blk: {
 | 
				
			||||||
 | 
								if (@typeInfo(T) == .Optional) {
 | 
				
			||||||
 | 
									break :blk self.get(?pg.Iterator(S), column_index) orelse return null;
 | 
				
			||||||
 | 
								} else {
 | 
				
			||||||
 | 
									break :blk self.get(pg.Iterator(S), column_index);
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							};
 | 
				
			||||||
 | 
							return try slice.alloc(allocator orelse return error.AllocatorRequiredForSliceMapping);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						const value = self.get(field.type, column_index);
 | 
				
			||||||
 | 
						const a = allocator orelse return value;
 | 
				
			||||||
 | 
						return mapValue(T, value, a);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub fn PgMapper(comptime T: type) type {
 | 
				
			||||||
 | 
						return struct {
 | 
				
			||||||
 | 
							result: *pg.Result,
 | 
				
			||||||
 | 
							allocator: ?std.mem.Allocator,
 | 
				
			||||||
 | 
							column_indexes: [std.meta.fields(T).len]?usize,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							const Self = @This();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							pub fn next(self: *const Self, row: *pg.Row) !?T {
 | 
				
			||||||
 | 
								var value: T = undefined;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								const allocator = self.allocator;
 | 
				
			||||||
 | 
								inline for (std.meta.fields(T), self.column_indexes) |field, optional_column_index| {
 | 
				
			||||||
 | 
									//TODO I must reimplement row.mapColumn because it's not public :-(
 | 
				
			||||||
 | 
									@field(value, field.name) = try rowMapColumn(row, &field, optional_column_index, allocator);
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								return value;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						};
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/// Make a PostgreSQL result mapper with the given prefix, if there is one.
 | 
					/// Make a PostgreSQL result mapper with the given prefix, if there is one.
 | 
				
			||||||
pub fn makeMapper(comptime T: type, result: *pg.Result, allocator: std.mem.Allocator, optionalPrefix: ?[]const u8) !pg.Mapper(T) {
 | 
					pub fn makeMapper(comptime T: type, result: *pg.Result, allocator: std.mem.Allocator, optionalPrefix: ?[]const u8) !PgMapper(T) {
 | 
				
			||||||
	var column_indexes: [std.meta.fields(T).len]?usize = undefined;
 | 
						var column_indexes: [std.meta.fields(T).len]?usize = undefined;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	inline for (std.meta.fields(T), 0..) |field, i| {
 | 
						inline for (std.meta.fields(T), 0..) |field, i| {
 | 
				
			||||||
| 
						 | 
					@ -80,21 +161,91 @@ pub fn makeMapper(comptime T: type, result: *pg.Result, allocator: std.mem.Alloc
 | 
				
			||||||
	};
 | 
						};
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// 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 inlineRelations: ?[]const _relations.ModelRelation) type {
 | 
				
			||||||
	const InstanceInterface = _result.QueryResultReader(TableShape, inlineRelations).Instance;
 | 
						const InstanceInterface = _result.QueryResultReader(TableShape, inlineRelations).Instance;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Build relations mappers container type.
 | 
				
			||||||
 | 
						const RelationsMappersType = comptime typeBuilder: {
 | 
				
			||||||
 | 
							if (inlineRelations) |_inlineRelations| {
 | 
				
			||||||
 | 
								// Make a field for each relation.
 | 
				
			||||||
 | 
								var fields: [_inlineRelations.len]std.builtin.Type.StructField = undefined;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								for (_inlineRelations, &fields) |relation, *field| {
 | 
				
			||||||
 | 
									// Get relation field type (TableShape of the related value).
 | 
				
			||||||
 | 
									var relationImpl = relation.relation{};
 | 
				
			||||||
 | 
									const relationInstanceType = @TypeOf(relationImpl.relation());
 | 
				
			||||||
 | 
									const relationFieldType = PgMapper(relationInstanceType.TableShape);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									field.* = .{
 | 
				
			||||||
 | 
										.name = relation.field ++ [0:0]u8{},
 | 
				
			||||||
 | 
										.type = relationFieldType,
 | 
				
			||||||
 | 
										.default_value = null,
 | 
				
			||||||
 | 
										.is_comptime = false,
 | 
				
			||||||
 | 
										.alignment = @alignOf(relationFieldType),
 | 
				
			||||||
 | 
									};
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								// Build type with one field for each relation.
 | 
				
			||||||
 | 
								break :typeBuilder @Type(std.builtin.Type{
 | 
				
			||||||
 | 
									.Struct = .{
 | 
				
			||||||
 | 
										.layout = std.builtin.Type.ContainerLayout.auto,
 | 
				
			||||||
 | 
										.fields = &fields,
 | 
				
			||||||
 | 
										.decls = &[0]std.builtin.Type.Declaration{},
 | 
				
			||||||
 | 
										.is_tuple = false,
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
								});
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// Build default empty type.
 | 
				
			||||||
 | 
							break :typeBuilder @Type(std.builtin.Type{
 | 
				
			||||||
 | 
								.Struct = .{
 | 
				
			||||||
 | 
									.layout = std.builtin.Type.ContainerLayout.auto,
 | 
				
			||||||
 | 
									.fields = &[0]std.builtin.Type.StructField{},
 | 
				
			||||||
 | 
									.decls = &[0]std.builtin.Type.Declaration{},
 | 
				
			||||||
 | 
									.is_tuple = false,
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
							});
 | 
				
			||||||
 | 
						};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	return struct {
 | 
						return struct {
 | 
				
			||||||
		const Self = @This();
 | 
							const Self = @This();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							/// PostgreSQL implementation of the query result reader instance.
 | 
				
			||||||
		pub const Instance = struct {
 | 
							pub const Instance = struct {
 | 
				
			||||||
			/// Main object mapper.
 | 
								/// Main object mapper.
 | 
				
			||||||
			mainMapper: pg.Mapper(TableShape) = undefined,
 | 
								mainMapper: PgMapper(TableShape) = undefined,
 | 
				
			||||||
 | 
								relationsMappers: RelationsMappersType = undefined,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			fn next(opaqueSelf: *anyopaque) !?TableShape { //TODO inline relations.
 | 
								fn next(opaqueSelf: *anyopaque) !?_result.TableWithRelations(TableShape, inlineRelations) {
 | 
				
			||||||
				const self: *Instance = @ptrCast(@alignCast(opaqueSelf));
 | 
									const self: *Instance = @ptrCast(@alignCast(opaqueSelf));
 | 
				
			||||||
				return try self.mainMapper.next();
 | 
					
 | 
				
			||||||
 | 
									// Try to get the next row.
 | 
				
			||||||
 | 
									var row: pg.Row = try self.mainMapper.result.next() orelse return null;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									// Get main table result.
 | 
				
			||||||
 | 
									const mainTable = try self.mainMapper.next(&row) orelse return null;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									// Initialize the result.
 | 
				
			||||||
 | 
									var result: _result.TableWithRelations(TableShape, inlineRelations) = undefined;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									// Copy each basic table field.
 | 
				
			||||||
 | 
									inline for (std.meta.fields(TableShape)) |field| {
 | 
				
			||||||
 | 
										@field(result, field.name) = @field(mainTable, field.name);
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									if (inlineRelations) |_inlineRelations| {
 | 
				
			||||||
 | 
										// For each relation, retrieve its value and put it in the result.
 | 
				
			||||||
 | 
										inline for (_inlineRelations) |relation| {
 | 
				
			||||||
 | 
											//TODO detect null relation.
 | 
				
			||||||
 | 
											@field(result, relation.field) = try @field(self.relationsMappers, relation.field).next(&row);
 | 
				
			||||||
 | 
										}
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									return result; // Return built result.
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								/// Get the generic reader instance instance.
 | 
				
			||||||
			pub fn instance(self: *Instance, allocator: std.mem.Allocator) InstanceInterface {
 | 
								pub fn instance(self: *Instance, allocator: std.mem.Allocator) InstanceInterface {
 | 
				
			||||||
				return .{
 | 
									return .{
 | 
				
			||||||
					.__interface = .{
 | 
										.__interface = .{
 | 
				
			||||||
| 
						 | 
					@ -115,9 +266,22 @@ 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 (inlineRelations) |_inlineRelations| {
 | 
				
			||||||
 | 
									// Initialize mapper for each 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) =
 | 
				
			||||||
 | 
											try makeMapper(relationInstanceType.TableShape, self.result, allocator, "relations." ++ relation.field ++ ".");
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			return self.instance.instance(allocator);
 | 
								return self.instance.instance(allocator);
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							/// Get the generic reader instance.
 | 
				
			||||||
		pub fn reader(self: *Self) _result.QueryResultReader(TableShape, inlineRelations) {
 | 
							pub fn reader(self: *Self) _result.QueryResultReader(TableShape, inlineRelations) {
 | 
				
			||||||
			return .{
 | 
								return .{
 | 
				
			||||||
				._interface = .{
 | 
									._interface = .{
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -43,8 +43,8 @@ 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 tt = relation.relation{};
 | 
									var relationImpl = relation.relation{};
 | 
				
			||||||
				const relationInstance = tt.relation();
 | 
									const relationInstance = relationImpl.relation();
 | 
				
			||||||
				if (relationInstance.inlineMapping()) {
 | 
									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)));
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -67,6 +67,10 @@ pub fn typedMany(
 | 
				
			||||||
	return struct {
 | 
						return struct {
 | 
				
			||||||
		const Self = @This();
 | 
							const Self = @This();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							fn getRepositoryConfiguration(_: *anyopaque) repository.RepositoryConfiguration(ToModel, ToTable) {
 | 
				
			||||||
 | 
								return toRepositoryConfig;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		fn inlineMapping(_: *anyopaque) bool {
 | 
							fn inlineMapping(_: *anyopaque) bool {
 | 
				
			||||||
			return false;
 | 
								return false;
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
| 
						 | 
					@ -109,11 +113,12 @@ pub fn typedMany(
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		pub fn relation(self: *Self) Relation {
 | 
							pub fn relation(self: *Self) Relation(ToModel, ToTable) {
 | 
				
			||||||
			return .{
 | 
								return .{
 | 
				
			||||||
				._interface = .{
 | 
									._interface = .{
 | 
				
			||||||
					.instance = self,
 | 
										.instance = self,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
										.getRepositoryConfiguration = getRepositoryConfiguration,
 | 
				
			||||||
					.inlineMapping = inlineMapping,
 | 
										.inlineMapping = inlineMapping,
 | 
				
			||||||
					.genJoin = genJoin,
 | 
										.genJoin = genJoin,
 | 
				
			||||||
					.genSelect = genSelect,
 | 
										.genSelect = genSelect,
 | 
				
			||||||
| 
						 | 
					@ -199,6 +204,10 @@ fn typedOne(
 | 
				
			||||||
	return struct {
 | 
						return struct {
 | 
				
			||||||
		const Self = @This();
 | 
							const Self = @This();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							fn getRepositoryConfiguration(_: *anyopaque) repository.RepositoryConfiguration(ToModel, ToTable) {
 | 
				
			||||||
 | 
								return toRepositoryConfig;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		fn inlineMapping(_: *anyopaque) bool {
 | 
							fn inlineMapping(_: *anyopaque) bool {
 | 
				
			||||||
			return true;
 | 
								return true;
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
| 
						 | 
					@ -267,11 +276,12 @@ fn typedOne(
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		pub fn relation(self: *Self) Relation {
 | 
							pub fn relation(self: *Self) Relation(ToModel, ToTable) {
 | 
				
			||||||
			return .{
 | 
								return .{
 | 
				
			||||||
				._interface = .{
 | 
									._interface = .{
 | 
				
			||||||
					.instance = self,
 | 
										.instance = self,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
										.getRepositoryConfiguration = getRepositoryConfiguration,
 | 
				
			||||||
					.inlineMapping = inlineMapping,
 | 
										.inlineMapping = inlineMapping,
 | 
				
			||||||
					.genJoin = genJoin,
 | 
										.genJoin = genJoin,
 | 
				
			||||||
					.genSelect = genSelect,
 | 
										.genSelect = genSelect,
 | 
				
			||||||
| 
						 | 
					@ -282,42 +292,52 @@ fn typedOne(
 | 
				
			||||||
	};
 | 
						};
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					 | 
				
			||||||
/// Generic model relation interface.
 | 
					/// Generic model relation interface.
 | 
				
			||||||
pub const Relation = struct {
 | 
					pub fn Relation(comptime ToModel: type, comptime ToTable: type) type {
 | 
				
			||||||
	const Self = @This();
 | 
						return struct {
 | 
				
			||||||
 | 
							const Self = @This();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	_interface: struct {
 | 
							pub const Model = ToModel;
 | 
				
			||||||
		instance: *anyopaque,
 | 
							pub const TableShape = ToTable;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		inlineMapping: *const fn (self: *anyopaque) bool,
 | 
							_interface: struct {
 | 
				
			||||||
		genJoin: *const fn (self: *anyopaque, comptime alias: []const u8) []const u8,
 | 
								instance: *anyopaque,
 | 
				
			||||||
		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,
 | 
					 | 
				
			||||||
	},
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	/// Relation mapping is done inline: this means that it's done at the same time the model is mapped,
 | 
								getRepositoryConfiguration: *const fn (self: *anyopaque) repository.RepositoryConfiguration(ToModel, ToTable),
 | 
				
			||||||
	/// and that the associated data will be retrieved in the main query.
 | 
								inlineMapping: *const fn (self: *anyopaque) bool,
 | 
				
			||||||
	pub fn inlineMapping(self: Self) bool {
 | 
								genJoin: *const fn (self: *anyopaque, comptime alias: []const u8) []const u8,
 | 
				
			||||||
		return self._interface.inlineMapping(self._interface.instance);
 | 
								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,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	/// In case of inline mapping, generate a JOIN clause to retrieve the associated data.
 | 
							/// Read the related model repository configuration.
 | 
				
			||||||
	pub fn genJoin(self: Self, comptime alias: []const u8) []const u8 {
 | 
							pub fn getRepositoryConfiguration(self: Self) repository.RepositoryConfiguration(ToModel, ToTable) {
 | 
				
			||||||
		return self._interface.genJoin(self._interface.instance, alias);
 | 
								return self._interface.getRepositoryConfiguration(self._interface.instance);
 | 
				
			||||||
	}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	/// Generate a SELECT clause to retrieve the associated data, with the given table and prefix.
 | 
							/// Relation mapping is done inline: this means that it's done at the same time the model is mapped,
 | 
				
			||||||
	pub fn genSelect(self: Self, comptime table: []const u8, comptime prefix: []const u8) []const u8 {
 | 
							/// and that the associated data will be retrieved in the main query.
 | 
				
			||||||
		return self._interface.genSelect(self._interface.instance, table, prefix);
 | 
							pub fn inlineMapping(self: Self) bool {
 | 
				
			||||||
	}
 | 
								return self._interface.inlineMapping(self._interface.instance);
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	/// Build the query to retrieve relation data.
 | 
							/// In case of inline mapping, generate a JOIN clause to retrieve the associated data.
 | 
				
			||||||
	/// Is always used when inline mapping is not possible, but also when loading relations lazily.
 | 
							pub fn genJoin(self: Self, comptime alias: []const u8) []const u8 {
 | 
				
			||||||
	pub fn buildQuery(self: Self, models: []const anyopaque, query: *anyopaque) !void {
 | 
								return self._interface.genJoin(self._interface.instance, alias);
 | 
				
			||||||
		return self._interface.buildQuery(self._interface.instance, models, query);
 | 
							}
 | 
				
			||||||
	}
 | 
					
 | 
				
			||||||
};
 | 
							/// 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);
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							/// 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, models: []const anyopaque, query: *anyopaque) !void {
 | 
				
			||||||
 | 
								return self._interface.buildQuery(self._interface.instance, models, query);
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						};
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/// A model relation object.
 | 
					/// A model relation object.
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -3,9 +3,73 @@ const zollections = @import("zollections");
 | 
				
			||||||
const _repository = @import("repository.zig");
 | 
					const _repository = @import("repository.zig");
 | 
				
			||||||
const _relations = @import("relations.zig");
 | 
					const _relations = @import("relations.zig");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// Type of a retrieved table data, with its retrieved relations.
 | 
				
			||||||
 | 
					pub fn TableWithRelations(comptime TableShape: type, comptime optionalRelations: ?[]const _relations.ModelRelation) type {
 | 
				
			||||||
 | 
						if (optionalRelations) |relations| {
 | 
				
			||||||
 | 
							const tableType = @typeInfo(TableShape);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// 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;
 | 
				
			||||||
 | 
							// Copy base table fields.
 | 
				
			||||||
 | 
							@memcpy(fields[0..tableType.Struct.fields.len], tableType.Struct.fields);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// For each relation, create a new struct field in the table shape.
 | 
				
			||||||
 | 
							for (relations, fields[tableType.Struct.fields.len..]) |relation, *field| {
 | 
				
			||||||
 | 
								// 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{
 | 
				
			||||||
 | 
									.Optional = .{
 | 
				
			||||||
 | 
										.child = relationInstanceType.TableShape
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
								});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								// Create the new field from relation data.
 | 
				
			||||||
 | 
								field.* = std.builtin.Type.StructField{
 | 
				
			||||||
 | 
									.name = relation.field ++ [0:0]u8{},
 | 
				
			||||||
 | 
									.type = relationFieldType,
 | 
				
			||||||
 | 
									.default_value = null,
 | 
				
			||||||
 | 
									.is_comptime = false,
 | 
				
			||||||
 | 
									.alignment = @alignOf(relationFieldType),
 | 
				
			||||||
 | 
								};
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// Build the new type.
 | 
				
			||||||
 | 
							return @Type(std.builtin.Type{
 | 
				
			||||||
 | 
								.Struct = .{
 | 
				
			||||||
 | 
									.layout = tableType.Struct.layout,
 | 
				
			||||||
 | 
									.fields = &fields,
 | 
				
			||||||
 | 
									.decls = tableType.Struct.decls,
 | 
				
			||||||
 | 
									.is_tuple = tableType.Struct.is_tuple,
 | 
				
			||||||
 | 
									.backing_integer = tableType.Struct.backing_integer,
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
							});
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							return TableShape;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// 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 {
 | 
				
			||||||
 | 
						if (optionalRelations) |_| {
 | 
				
			||||||
 | 
							// Make a structure of TableShape type.
 | 
				
			||||||
 | 
							var tableValue: TableShape = undefined;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// Copy all fields of the table shape in the new structure.
 | 
				
			||||||
 | 
							inline for (std.meta.fields(TableShape)) |field| {
 | 
				
			||||||
 | 
								@field(tableValue, field.name) = @field(value, field.name);
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// Return the simplified structure.
 | 
				
			||||||
 | 
							return tableValue;
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							// No relations, it should already be of type TableShape.
 | 
				
			||||||
 | 
							return value;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/// 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 inlineRelations: ?[]const _relations.ModelRelation) type {
 | 
				
			||||||
	_ = inlineRelations;
 | 
					 | 
				
			||||||
	return struct {
 | 
						return struct {
 | 
				
			||||||
		const Self = @This();
 | 
							const Self = @This();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -13,12 +77,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!?TableShape, //TODO inline relations.
 | 
									next: *const fn (self: *anyopaque) anyerror!?TableWithRelations(TableShape, inlineRelations),
 | 
				
			||||||
			},
 | 
								},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			allocator: std.mem.Allocator,
 | 
								allocator: std.mem.Allocator,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			pub fn next(self: Instance) !?TableShape {
 | 
								pub fn next(self: Instance) !?TableWithRelations(TableShape, inlineRelations) {
 | 
				
			||||||
				return self.__interface.next(self.__interface.instance);
 | 
									return self.__interface.next(self.__interface.instance);
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
		};
 | 
							};
 | 
				
			||||||
| 
						 | 
					@ -55,8 +119,23 @@ pub fn ResultMapper(comptime Model: type, comptime TableShape: type, comptime re
 | 
				
			||||||
			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(Model);
 | 
				
			||||||
				model.* = try repositoryConfig.fromSql(rawModel);
 | 
									model.* = try repositoryConfig.fromSql(toTableShape(TableShape, inlineRelations, rawModel));
 | 
				
			||||||
				//TODO inline relations.
 | 
					
 | 
				
			||||||
 | 
									// Map inline relations.
 | 
				
			||||||
 | 
									if (inlineRelations) |_inlineRelations| {
 | 
				
			||||||
 | 
										// If there are loaded inline relations, map them to the result.
 | 
				
			||||||
 | 
										inline for (_inlineRelations) |relation| {
 | 
				
			||||||
 | 
											comptime var relationImpl = relation.relation{};
 | 
				
			||||||
 | 
											const relationInstance = relationImpl.relation();
 | 
				
			||||||
 | 
											// Set the read inline relation value.
 | 
				
			||||||
 | 
											@field(model.*, relation.field) = (
 | 
				
			||||||
 | 
												if (@field(rawModel, relation.field)) |relationVal|
 | 
				
			||||||
 | 
													try relationInstance.getRepositoryConfiguration().fromSql(relationVal)
 | 
				
			||||||
 | 
												else null
 | 
				
			||||||
 | 
											);
 | 
				
			||||||
 | 
										}
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
				try models.append(model);
 | 
									try models.append(model);
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
	Add table
		
		Reference in a new issue