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