Set relations to retrieve by a query at compile time.
+ Add compile-time utility functions to build strings and queries. * Relations to retrieve when querying a model must now be set at comptime.
This commit is contained in:
		
							parent
							
								
									5a2964622c
								
							
						
					
					
						commit
						04b61f9787
					
				
					 6 changed files with 148 additions and 137 deletions
				
			
		
							
								
								
									
										33
									
								
								src/comptime.zig
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								src/comptime.zig
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,33 @@
 | 
			
		|||
 | 
			
		||||
/// Append an element to the given array at comptime.
 | 
			
		||||
pub fn append(array: anytype, element: anytype) @TypeOf(array ++ .{element}) {
 | 
			
		||||
	return array ++ .{element};
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Join strings into one, with the given separator in between.
 | 
			
		||||
pub fn join(separator: []const u8, slices: []const[]const u8) []const u8 {
 | 
			
		||||
	if (slices.len == 0) return "";
 | 
			
		||||
 | 
			
		||||
	// Compute total length of the string to make.
 | 
			
		||||
	const totalLen = total: {
 | 
			
		||||
		// Compute separator length.
 | 
			
		||||
		var total = separator.len * (slices.len - 1);
 | 
			
		||||
		// Add length of all slices.
 | 
			
		||||
		for (slices) |slice| total += slice.len;
 | 
			
		||||
		break :total total;
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	var buffer: [totalLen]u8 = undefined;
 | 
			
		||||
 | 
			
		||||
	// Based on std.mem.joinMaybeZ implementation.
 | 
			
		||||
	@memcpy(buffer[0..slices[0].len], slices[0]);
 | 
			
		||||
	var buf_index: usize = slices[0].len;
 | 
			
		||||
	for (slices[1..]) |slice| {
 | 
			
		||||
		@memcpy(buffer[buf_index .. buf_index + separator.len], separator);
 | 
			
		||||
		buf_index += separator.len;
 | 
			
		||||
		@memcpy(buffer[buf_index .. buf_index + slice.len], slice);
 | 
			
		||||
		buf_index += slice.len;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return &buffer;
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										155
									
								
								src/query.zig
									
										
									
									
									
								
							
							
						
						
									
										155
									
								
								src/query.zig
									
										
									
									
									
								
							| 
						 | 
				
			
			@ -8,25 +8,71 @@ const _sql = @import("sql.zig");
 | 
			
		|||
const _conditions = @import("conditions.zig");
 | 
			
		||||
const relations = @import("relations.zig");
 | 
			
		||||
const repository = @import("repository.zig");
 | 
			
		||||
 | 
			
		||||
const InlineRelationsResult = struct {
 | 
			
		||||
 | 
			
		||||
};
 | 
			
		||||
const _comptime = @import("comptime.zig");
 | 
			
		||||
 | 
			
		||||
/// Repository query configuration structure.
 | 
			
		||||
pub const RepositoryQueryConfiguration = struct {
 | 
			
		||||
	select: ?_sql.RawQuery = null,
 | 
			
		||||
	join: ?_sql.RawQuery = null,
 | 
			
		||||
	where: ?_sql.RawQuery = null,
 | 
			
		||||
	with: ?[]const relations.Eager = null,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/// Compiled relations structure.
 | 
			
		||||
const CompiledRelations = struct {
 | 
			
		||||
	inlineSelect: []const u8,
 | 
			
		||||
	inlineJoins: []const u8,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/// Repository models query manager.
 | 
			
		||||
/// Manage query string build and its execution.
 | 
			
		||||
pub fn RepositoryQuery(comptime Model: type, comptime TableShape: type, comptime repositoryConfig: repository.RepositoryConfiguration(Model, TableShape)) type {
 | 
			
		||||
	// Pre-compute SQL buffer size.
 | 
			
		||||
pub fn RepositoryQuery(comptime Model: type, comptime TableShape: type, comptime repositoryConfig: repository.RepositoryConfiguration(Model, TableShape), comptime with: ?[]const relations.ModelRelation) type {
 | 
			
		||||
	const compiledRelations = comptime compile: {
 | 
			
		||||
		// Inline relations list.
 | 
			
		||||
		var inlineRelations: []relations.ModelRelation = &[0]relations.ModelRelation{};
 | 
			
		||||
 | 
			
		||||
		if (with) |_with| {
 | 
			
		||||
			// If there are relations to eager load, prepare their query.
 | 
			
		||||
 | 
			
		||||
			// Initialize inline select array.
 | 
			
		||||
			var inlineSelect: [][]const u8 = &[0][]const u8{};
 | 
			
		||||
			// Initialize inline joins array.
 | 
			
		||||
			var inlineJoins: [][]const u8 = &[0][]const u8{};
 | 
			
		||||
 | 
			
		||||
			for (_with) |relation| {
 | 
			
		||||
				// For each relation, determine if it's inline or not.
 | 
			
		||||
				var tt = relation.relation{};
 | 
			
		||||
				const relationInstance = tt.relation();
 | 
			
		||||
				if (relationInstance.inlineMapping()) {
 | 
			
		||||
					// Add the current relation to inline relations.
 | 
			
		||||
					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.
 | 
			
		||||
					inlineSelect = @ptrCast(@constCast(_comptime.append(inlineSelect, relationInstance.genSelect(tableAlias, fieldsPrefix))));
 | 
			
		||||
					// Generate joined table for the relation.
 | 
			
		||||
					inlineJoins = @ptrCast(@constCast(_comptime.append(inlineJoins, relationInstance.genJoin(tableAlias))));
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			break :compile CompiledRelations{
 | 
			
		||||
				.inlineSelect = if (inlineSelect.len > 0) ", " ++ _comptime.join(", ", inlineSelect) else "",
 | 
			
		||||
				.inlineJoins = if (inlineJoins.len > 0) " " ++ _comptime.join(" ", inlineJoins) else "",
 | 
			
		||||
			};
 | 
			
		||||
		} else {
 | 
			
		||||
			break :compile CompiledRelations{
 | 
			
		||||
				.inlineSelect = "",
 | 
			
		||||
				.inlineJoins = "",
 | 
			
		||||
			};
 | 
			
		||||
		}
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	// Pre-compute SQL buffer.
 | 
			
		||||
	const fromClause = " FROM \"" ++ repositoryConfig.table ++ "\"";
 | 
			
		||||
	const defaultSelectSql = "\"" ++ repositoryConfig.table ++ "\".*";
 | 
			
		||||
	const defaultSelectSql = "\"" ++ repositoryConfig.table ++ "\".*" ++ compiledRelations.inlineSelect;
 | 
			
		||||
	const defaultJoin = compiledRelations.inlineJoins;
 | 
			
		||||
 | 
			
		||||
	// Model key type.
 | 
			
		||||
	const KeyType = repository.ModelKeyType(Model, TableShape, repositoryConfig);
 | 
			
		||||
| 
						 | 
				
			
			@ -39,9 +85,6 @@ pub fn RepositoryQuery(comptime Model: type, comptime TableShape: type, comptime
 | 
			
		|||
		connection: *database.Connection = undefined,
 | 
			
		||||
		queryConfig: RepositoryQueryConfiguration,
 | 
			
		||||
 | 
			
		||||
		/// List of loaded inline relations.
 | 
			
		||||
		inlineRelations: []relations.Eager = undefined,
 | 
			
		||||
 | 
			
		||||
		query: ?_sql.RawQuery = null,
 | 
			
		||||
		sql: ?[]const u8 = null,
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -177,103 +220,16 @@ pub fn RepositoryQuery(comptime Model: type, comptime TableShape: type, comptime
 | 
			
		|||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		/// Set relations to eager load.
 | 
			
		||||
		pub fn with(self: *Self, relation: relations.ModelRelation) !void {
 | 
			
		||||
			// Take an array of eager relations (which can have subrelations).
 | 
			
		||||
			const allocator = self.arena.allocator();
 | 
			
		||||
 | 
			
		||||
			// Make a relation instance.
 | 
			
		||||
			const relationInstance = try allocator.create(relation.relation);
 | 
			
		||||
 | 
			
		||||
			// Add the new relation to a newly allocated array, with one more space.
 | 
			
		||||
			const newPos = if (self.queryConfig.with) |_with| _with.len else 0;
 | 
			
		||||
			var newWith = try allocator.alloc(relations.Eager, newPos + 1);
 | 
			
		||||
			newWith[newPos] = .{
 | 
			
		||||
				.field = relation.field,
 | 
			
		||||
				.relation = relationInstance.*.relation(),
 | 
			
		||||
				.with = &[0]relations.Eager{}, //TODO handle subrelations with dotted syntax
 | 
			
		||||
			};
 | 
			
		||||
 | 
			
		||||
			if (self.queryConfig.with) |_with| {
 | 
			
		||||
				// Copy existing relations.
 | 
			
		||||
				@memcpy(newWith[0..newPos], _with);
 | 
			
		||||
				// Free previous array.
 | 
			
		||||
				allocator.free(_with);
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			// Save the newly allocated array.
 | 
			
		||||
			self.queryConfig.with = newWith;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		/// Build inline relations query part.
 | 
			
		||||
		fn buildInlineRelations(self: *Self) !?struct{
 | 
			
		||||
			select: []const u8,
 | 
			
		||||
			join: _sql.RawQuery,
 | 
			
		||||
		} {
 | 
			
		||||
			if (self.queryConfig.with) |_with| {
 | 
			
		||||
				// Initialize an ArrayList of query parts for relations.
 | 
			
		||||
				var inlineRelations = try std.ArrayList(_sql.RawQuery).initCapacity(self.arena.allocator(), _with.len);
 | 
			
		||||
				defer inlineRelations.deinit();
 | 
			
		||||
				var inlineRelationsSelect = try std.ArrayList([]const u8).initCapacity(self.arena.allocator(), _with.len);
 | 
			
		||||
				defer inlineRelationsSelect.deinit();
 | 
			
		||||
 | 
			
		||||
				// Initialize an ArrayList to store all loaded inline relations.
 | 
			
		||||
				var loadedRelations = std.ArrayList(relations.Eager).init(self.arena.allocator());
 | 
			
		||||
				defer loadedRelations.deinit();
 | 
			
		||||
 | 
			
		||||
				for (_with) |_relation| {
 | 
			
		||||
					// Append each inline relation to the ArrayList.
 | 
			
		||||
					if (_relation.relation.inlineMapping()) {
 | 
			
		||||
						try loadedRelations.append(_relation); // Store the loaded inline relation.
 | 
			
		||||
 | 
			
		||||
						// Get an allocator for local allocations.
 | 
			
		||||
						const localAllocator = self.arena.allocator();
 | 
			
		||||
 | 
			
		||||
						// Build table alias and fields prefix.
 | 
			
		||||
						const tableAlias = try std.fmt.allocPrint(localAllocator, "relations.{s}", .{_relation.field});
 | 
			
		||||
						defer localAllocator.free(tableAlias);
 | 
			
		||||
						const prefix = try std.fmt.allocPrint(localAllocator, "{s}.", .{tableAlias});
 | 
			
		||||
						defer localAllocator.free(prefix);
 | 
			
		||||
 | 
			
		||||
						// Alter query to get relation fields.
 | 
			
		||||
						try inlineRelations.append(try _relation.relation.genJoin(self.arena.allocator(), tableAlias));
 | 
			
		||||
						const relationSelect = try _relation.relation.genSelect(localAllocator, tableAlias, prefix);
 | 
			
		||||
						try inlineRelationsSelect.append(relationSelect);
 | 
			
		||||
					}
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				self.inlineRelations = try loadedRelations.toOwnedSlice();
 | 
			
		||||
 | 
			
		||||
				// Return the inline relations query part.
 | 
			
		||||
				return .{
 | 
			
		||||
					.select = try std.mem.join(self.arena.allocator(), ", ", inlineRelationsSelect.items),
 | 
			
		||||
					.join = try _sql.RawQuery.fromConcat(self.arena.allocator(), inlineRelations.items),
 | 
			
		||||
				};
 | 
			
		||||
			} else {
 | 
			
		||||
				// Nothing.
 | 
			
		||||
				return null;
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		/// Build SQL query.
 | 
			
		||||
		pub fn buildSql(self: *Self) !void {
 | 
			
		||||
			// Build inline relations query part.
 | 
			
		||||
			const inlineRelations = try self.buildInlineRelations();
 | 
			
		||||
			defer if (inlineRelations) |_inlineRelations| self.arena.allocator().free(_inlineRelations.join.sql);
 | 
			
		||||
			defer if (inlineRelations) |_inlineRelations| self.arena.allocator().free(_inlineRelations.join.params);
 | 
			
		||||
			defer if (inlineRelations) |_inlineRelations| self.arena.allocator().free(_inlineRelations.select);
 | 
			
		||||
 | 
			
		||||
			// Build the full SQL query from all its parts.
 | 
			
		||||
			const sqlQuery = _sql.RawQuery{
 | 
			
		||||
				.sql = try std.mem.join(self.arena.allocator(), "", &[_][]const u8{
 | 
			
		||||
					"SELECT ", if (self.queryConfig.select) |_select| _select.sql else defaultSelectSql,
 | 
			
		||||
					if (inlineRelations) |_| ", " else "",
 | 
			
		||||
					if (inlineRelations) |_inlineRelations| _inlineRelations.select else "",
 | 
			
		||||
					fromClause,
 | 
			
		||||
					defaultJoin,
 | 
			
		||||
					if (self.queryConfig.join) |_| " " else "",
 | 
			
		||||
					if (self.queryConfig.join) |_join| _join.sql else "",
 | 
			
		||||
					if (inlineRelations) |_| " " else "",
 | 
			
		||||
					if (inlineRelations) |_inlineRelations| _inlineRelations.join.sql else "",
 | 
			
		||||
					if (self.queryConfig.where) |_| " WHERE " else "",
 | 
			
		||||
					if (self.queryConfig.where) |_where| _where.sql else "",
 | 
			
		||||
					";",
 | 
			
		||||
| 
						 | 
				
			
			@ -281,7 +237,6 @@ pub fn RepositoryQuery(comptime Model: type, comptime TableShape: type, comptime
 | 
			
		|||
				.params = try std.mem.concat(self.arena.allocator(), _sql.RawQueryParameter, &[_][]const _sql.RawQueryParameter{
 | 
			
		||||
					if (self.queryConfig.select) |_select| _select.params else &[0]_sql.RawQueryParameter{},
 | 
			
		||||
					if (self.queryConfig.join) |_join| _join.params else &[0]_sql.RawQueryParameter{},
 | 
			
		||||
					if (inlineRelations) |_inlineRelations| _inlineRelations.join.params else &[0]_sql.RawQueryParameter{},
 | 
			
		||||
					if (self.queryConfig.where) |_where| _where.params else &[0]_sql.RawQueryParameter{},
 | 
			
		||||
				})
 | 
			
		||||
			};
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -62,8 +62,7 @@ pub fn typedMany(
 | 
			
		|||
	};
 | 
			
		||||
 | 
			
		||||
	const FromKeyType = std.meta.fields(FromModel)[std.meta.fieldIndex(FromModel, fromRepositoryConfig.key[0]).?].type;
 | 
			
		||||
	const QueryType = _query.RepositoryQuery(ToModel, ToTable, toRepositoryConfig);
 | 
			
		||||
	const SelectBuilder = _sql.SelectBuilder(ToTable);
 | 
			
		||||
	const QueryType = _query.RepositoryQuery(ToModel, ToTable, toRepositoryConfig, null);
 | 
			
		||||
 | 
			
		||||
	return struct {
 | 
			
		||||
		const Self = @This();
 | 
			
		||||
| 
						 | 
				
			
			@ -72,12 +71,12 @@ pub fn typedMany(
 | 
			
		|||
			return false;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		fn genJoin(_: *anyopaque, _: std.mem.Allocator, _: []const u8) !_sql.RawQuery {
 | 
			
		||||
		fn genJoin(_: *anyopaque, comptime _: []const u8) []const u8 {
 | 
			
		||||
			unreachable; // No possible join in a many relation.
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		fn genSelect(_: *anyopaque, allocator: std.mem.Allocator, table: []const u8, prefix: []const u8) ![]const u8 {
 | 
			
		||||
			return SelectBuilder.build(allocator, table, prefix);
 | 
			
		||||
		fn genSelect(_: *anyopaque, comptime table: []const u8, comptime prefix: []const u8) []const u8 {
 | 
			
		||||
			return _sql.SelectBuild(ToTable, table, prefix);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		fn buildQuery(_: *anyopaque, opaqueModels: []const anyopaque, opaqueQuery: *anyopaque) !void {
 | 
			
		||||
| 
						 | 
				
			
			@ -181,8 +180,7 @@ fn typedOne(
 | 
			
		|||
	comptime config: OneConfiguration) type {
 | 
			
		||||
 | 
			
		||||
	const FromKeyType = std.meta.fields(FromModel)[std.meta.fieldIndex(FromModel, fromRepositoryConfig.key[0]).?].type;
 | 
			
		||||
	const QueryType = _query.RepositoryQuery(ToModel, ToTable, toRepositoryConfig);
 | 
			
		||||
	const SelectBuilder = _sql.SelectBuilder(ToTable);
 | 
			
		||||
	const QueryType = _query.RepositoryQuery(ToModel, ToTable, toRepositoryConfig, null);
 | 
			
		||||
 | 
			
		||||
	// Get foreign key from relation config or repository config.
 | 
			
		||||
	const foreignKey = switch (config) {
 | 
			
		||||
| 
						 | 
				
			
			@ -205,32 +203,29 @@ fn typedOne(
 | 
			
		|||
			return true;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		fn genJoin(_: *anyopaque, allocator: std.mem.Allocator, alias: []const u8) !_sql.RawQuery {
 | 
			
		||||
		fn genJoin(_: *anyopaque, comptime alias: []const u8) []const u8 {
 | 
			
		||||
			return switch (config) {
 | 
			
		||||
				.direct => (.{
 | 
			
		||||
					.sql = try std.fmt.allocPrint(allocator, "LEFT JOIN \"" ++ toRepositoryConfig.table ++ "\" AS \"{s}\" ON " ++
 | 
			
		||||
						"\"" ++ fromRepositoryConfig.table ++ "\"." ++ foreignKey ++ " = \"{s}\"." ++ modelKey, .{alias, alias}),
 | 
			
		||||
					.params = &[0]_sql.RawQueryParameter{},
 | 
			
		||||
				}),
 | 
			
		||||
				.direct => (
 | 
			
		||||
					"LEFT JOIN \"" ++ toRepositoryConfig.table ++ "\" AS \"" ++ alias ++ "\" ON " ++
 | 
			
		||||
						"\"" ++ fromRepositoryConfig.table ++ "\"." ++ foreignKey ++ " = \"" ++ alias ++ "\"." ++ modelKey
 | 
			
		||||
				),
 | 
			
		||||
 | 
			
		||||
				.reverse => (.{
 | 
			
		||||
					.sql = try std.fmt.allocPrint(allocator, "LEFT JOIN \"" ++ toRepositoryConfig.table ++ "\" AS \"{s}\" ON " ++
 | 
			
		||||
						"\"" ++ fromRepositoryConfig.table ++ "\"." ++ modelKey ++ " = \"{s}\"." ++ foreignKey, .{alias, alias}),
 | 
			
		||||
					.params = &[0]_sql.RawQueryParameter{},
 | 
			
		||||
				}),
 | 
			
		||||
				.reverse => (
 | 
			
		||||
					"LEFT JOIN \"" ++ toRepositoryConfig.table ++ "\" AS \"" ++ alias ++ "\" ON " ++
 | 
			
		||||
						"\"" ++ fromRepositoryConfig.table ++ "\"." ++ modelKey ++ " = \"" ++ alias ++ "\"." ++ foreignKey
 | 
			
		||||
				),
 | 
			
		||||
 | 
			
		||||
				.through => |through| (.{
 | 
			
		||||
					.sql = try std.fmt.allocPrint(allocator, "LEFT JOIN \"" ++ through.table ++ "\" AS \"{s}_pivot\" ON " ++
 | 
			
		||||
						"\"" ++ fromRepositoryConfig.table ++ "\"." ++ foreignKey ++ " = " ++ "\"{s}_pivot\"." ++ through.joinForeignKey ++
 | 
			
		||||
						"LEFT JOIN \"" ++ toRepositoryConfig.table ++ "\" AS \"{s}\" ON " ++
 | 
			
		||||
						"\"{s}_pivot\"." ++ through.joinModelKey ++ " = " ++ "\"{s}\"." ++ modelKey, .{alias, alias, alias, alias, alias}),
 | 
			
		||||
					.params = &[0]_sql.RawQueryParameter{},
 | 
			
		||||
				}),
 | 
			
		||||
				.through => |through| (
 | 
			
		||||
					"LEFT JOIN \"" ++ through.table ++ "\" AS \"" ++ alias ++ "_pivot\" ON " ++
 | 
			
		||||
						"\"" ++ fromRepositoryConfig.table ++ "\"." ++ foreignKey ++ " = " ++ "\"" ++ alias ++ "_pivot\"." ++ through.joinForeignKey ++
 | 
			
		||||
					"LEFT JOIN \"" ++ toRepositoryConfig.table ++ "\" AS \"" ++ alias ++ "\" ON " ++
 | 
			
		||||
						"\"" ++ alias ++ "_pivot\"." ++ through.joinModelKey ++ " = " ++ "\"" ++ alias ++ "\"." ++ modelKey
 | 
			
		||||
				),
 | 
			
		||||
			};
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		fn genSelect(_: *anyopaque, allocator: std.mem.Allocator, table: []const u8, prefix: []const u8) ![]const u8 {
 | 
			
		||||
			return SelectBuilder.build(allocator, table, prefix);
 | 
			
		||||
		fn genSelect(_: *anyopaque, comptime table: []const u8, comptime prefix: []const u8) []const u8 {
 | 
			
		||||
			return _sql.SelectBuild(ToTable, table, prefix);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		fn buildQuery(_: *anyopaque, opaqueModels: []const anyopaque, opaqueQuery: *anyopaque) !void {
 | 
			
		||||
| 
						 | 
				
			
			@ -296,8 +291,8 @@ pub const Relation = struct {
 | 
			
		|||
		instance: *anyopaque,
 | 
			
		||||
 | 
			
		||||
		inlineMapping: *const fn (self: *anyopaque) bool,
 | 
			
		||||
		genJoin: *const fn (self: *anyopaque, allocator: std.mem.Allocator, alias: []const u8) anyerror!_sql.RawQuery,
 | 
			
		||||
		genSelect: *const fn (self: *anyopaque, allocator: std.mem.Allocator, table: []const u8, prefix: []const u8) anyerror![]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,
 | 
			
		||||
		buildQuery: *const fn (self: *anyopaque, models: []const anyopaque, query: *anyopaque) anyerror!void,
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -308,13 +303,13 @@ pub const Relation = struct {
 | 
			
		|||
	}
 | 
			
		||||
 | 
			
		||||
	/// In case of inline mapping, generate a JOIN clause to retrieve the associated data.
 | 
			
		||||
	pub fn genJoin(self: Self, allocator: std.mem.Allocator, alias: []const u8) !_sql.RawQuery {
 | 
			
		||||
		return self._interface.genJoin(self._interface.instance, allocator, alias);
 | 
			
		||||
	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, allocator: std.mem.Allocator, table: []const u8, prefix: []const u8) ![]const u8 {
 | 
			
		||||
		return self._interface.genSelect(self._interface.instance, allocator, table, 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.
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -110,7 +110,7 @@ pub fn Repository(comptime Model: type, comptime TableShape: type, comptime repo
 | 
			
		|||
		pub const TableType = TableShape;
 | 
			
		||||
		pub const config = repositoryConfig;
 | 
			
		||||
 | 
			
		||||
		pub const Query: type = query.RepositoryQuery(Model, TableShape, config);
 | 
			
		||||
		pub const Query: type = query.RepositoryQuery(Model, TableShape, config, null);
 | 
			
		||||
		pub const Insert: type = insert.RepositoryInsert(Model, TableShape, config, config.insertShape);
 | 
			
		||||
 | 
			
		||||
		/// Type of one model key.
 | 
			
		||||
| 
						 | 
				
			
			@ -152,6 +152,10 @@ pub fn Repository(comptime Model: type, comptime TableShape: type, comptime repo
 | 
			
		|||
			}
 | 
			
		||||
		};
 | 
			
		||||
 | 
			
		||||
		pub fn QueryWith(comptime with: []const _relations.ModelRelation) type {
 | 
			
		||||
			return query.RepositoryQuery(Model, TableShape, config, with);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		pub fn InsertCustom(comptime InsertShape: type) type {
 | 
			
		||||
			return insert.RepositoryInsert(Model, TableShape, config, InsertShape);
 | 
			
		||||
		}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										23
									
								
								src/sql.zig
									
										
									
									
									
								
							
							
						
						
									
										23
									
								
								src/sql.zig
									
										
									
									
									
								
							| 
						 | 
				
			
			@ -213,6 +213,29 @@ pub fn SelectBuilder(comptime TableShape: type) type {
 | 
			
		|||
	};
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// 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 {
 | 
			
		||||
	// Initialize the selected columns string.
 | 
			
		||||
	var columnsSelect: []const u8 = "";
 | 
			
		||||
 | 
			
		||||
	var first = true;
 | 
			
		||||
	// For each field, generate a format string.
 | 
			
		||||
	for (@typeInfo(TableShape).Struct.fields) |field| {
 | 
			
		||||
		// Add ", " between all selected columns (just not the first one).
 | 
			
		||||
		if (first) {
 | 
			
		||||
			first = false;
 | 
			
		||||
		} else {
 | 
			
		||||
			columnsSelect = @ptrCast(columnsSelect ++ ", ");
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// Select the current field column.
 | 
			
		||||
		columnsSelect = @ptrCast(columnsSelect ++ "\"" ++ table ++ "\".\"" ++ field.name ++ "\" AS \"" ++ prefix ++ field.name ++ "\"");
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Return built columns selection.
 | 
			
		||||
	return columnsSelect;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -32,10 +32,11 @@ test "belongsTo" {
 | 
			
		|||
	};
 | 
			
		||||
 | 
			
		||||
	// Build a query of submodels.
 | 
			
		||||
	var myQuery = repository.MySubmodelRepository.Query.init(std.testing.allocator, poolConnector.connector(), .{});
 | 
			
		||||
	var myQuery = repository.MySubmodelRepository.QueryWith(
 | 
			
		||||
		// Retrieve parents of submodels from relation.
 | 
			
		||||
		&[_]zrm.relations.ModelRelation{repository.MySubmodelRelations.parent}
 | 
			
		||||
	).init(std.testing.allocator, poolConnector.connector(), .{});
 | 
			
		||||
	defer myQuery.deinit();
 | 
			
		||||
	// Retrieve parents of submodels from relation.
 | 
			
		||||
	try myQuery.with(repository.MySubmodelRelations.parent);
 | 
			
		||||
 | 
			
		||||
	try myQuery.buildSql();
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
	Add table
		
		Reference in a new issue