From e33bc5aaa27168e6e2c6d87b63454d29ee2b0646 Mon Sep 17 00:00:00 2001 From: Madeorsk Date: Sat, 23 Nov 2024 18:18:41 +0100 Subject: [PATCH] Create generic query result mapper and implement it for PostgreSQL. + Add generic query result mapper. + Add query result mapper implementation for PostgreSQL. --- src/insert.zig | 7 ++++- src/postgresql.zig | 77 +++++++++++++++++++++++++++++++--------------- src/query.zig | 13 +++++++- src/result.zig | 72 +++++++++++++++++++++++++++++++++++++++++++ src/update.zig | 7 ++++- 5 files changed, 148 insertions(+), 28 deletions(-) create mode 100644 src/result.zig diff --git a/src/insert.zig b/src/insert.zig index 0d251e7..0d9b4c0 100644 --- a/src/insert.zig +++ b/src/insert.zig @@ -6,6 +6,7 @@ const database = @import("database.zig"); const postgresql = @import("postgresql.zig"); const _sql = @import("sql.zig"); const repository = @import("repository.zig"); +const _result = @import("result.zig"); /// Type of an insertable column. Insert shape should be composed of only these. fn InsertableColumn(comptime ValueType: type) type { @@ -118,6 +119,9 @@ pub fn RepositoryInsert(comptime Model: type, comptime TableShape: type, comptim const Configuration = RepositoryInsertConfiguration(InsertShape); + /// Result mapper type. + pub const ResultMapper = _result.ResultMapper(Model, TableShape, repositoryConfig, null, null); + arena: std.heap.ArenaAllocator, connector: database.Connector, connection: *database.Connection = undefined, @@ -332,7 +336,8 @@ pub fn RepositoryInsert(comptime Model: type, comptime TableShape: type, comptim defer queryResult.deinit(); // Map query results. - return postgresql.mapResults(Model, TableShape, repositoryConfig, allocator, queryResult); + var postgresqlReader = postgresql.QueryResultReader(TableShape, null).init(queryResult); + return try ResultMapper.map(allocator, postgresqlReader.reader()); } /// Initialize a new repository insert query. diff --git a/src/postgresql.zig b/src/postgresql.zig index cbb9198..677fa2b 100644 --- a/src/postgresql.zig +++ b/src/postgresql.zig @@ -7,6 +7,7 @@ const database = @import("database.zig"); const _sql = @import("sql.zig"); const _relations = @import("relations.zig"); const repository = @import("repository.zig"); +const _result = @import("result.zig"); /// PostgreSQL query error details. pub const PostgresqlError = struct { @@ -79,32 +80,58 @@ pub fn makeMapper(comptime T: type, result: *pg.Result, allocator: std.mem.Alloc }; } -/// Generic query results mapping. -pub fn mapResults(comptime Model: type, comptime TableShape: type, - repositoryConfig: repository.RepositoryConfiguration(Model, TableShape), - allocator: std.mem.Allocator, queryResult: *pg.Result) !repository.RepositoryResult(Model) -{ - //TODO make a generic mapper and do it in repository.zig? - // Create an arena for mapper data. - var mapperArena = std.heap.ArenaAllocator.init(allocator); - // Get result mapper. - const mapper = try makeMapper(TableShape, queryResult, mapperArena.allocator(), null); +pub fn QueryResultReader(comptime TableShape: type, comptime inlineRelations: ?[]const _relations.ModelRelation) type { + const InstanceInterface = _result.QueryResultReader(TableShape, inlineRelations).Instance; - // Initialize models list. - var models = std.ArrayList(*Model).init(allocator); - defer models.deinit(); + return struct { + const Self = @This(); - // Get all raw models from the result mapper. - while (try mapper.next()) |rawModel| { - // Parse each raw model from the mapper. - const model = try allocator.create(Model); - model.* = try repositoryConfig.fromSql(rawModel); - try models.append(model); - } + pub const Instance = struct { + /// Main object mapper. + mainMapper: pg.Mapper(TableShape) = undefined, - // Return a result with the models. - return repository.RepositoryResult(Model).init(allocator, - zollections.Collection(Model).init(allocator, try models.toOwnedSlice()), - mapperArena, - ); + fn next(opaqueSelf: *anyopaque) !?TableShape { //TODO inline relations. + const self: *Instance = @ptrCast(@alignCast(opaqueSelf)); + return try self.mainMapper.next(); + } + + pub fn instance(self: *Instance, allocator: std.mem.Allocator) InstanceInterface { + return .{ + .__interface = .{ + .instance = self, + .next = next, + }, + + .allocator = allocator, + }; + } + }; + + instance: Instance = Instance{}, + + /// The PostgreSQL query result. + result: *pg.Result, + + fn initInstance(opaqueSelf: *anyopaque, allocator: std.mem.Allocator) !InstanceInterface { + const self: *Self = @ptrCast(@alignCast(opaqueSelf)); + self.instance.mainMapper = try makeMapper(TableShape, self.result, allocator, null); + return self.instance.instance(allocator); + } + + pub fn reader(self: *Self) _result.QueryResultReader(TableShape, inlineRelations) { + return .{ + ._interface = .{ + .instance = self, + .init = initInstance, + }, + }; + } + + /// Initialize a PostgreSQL query result reader from the given query result. + pub fn init(result: *pg.Result) Self { + return .{ + .result = result, + }; + } + }; } diff --git a/src/query.zig b/src/query.zig index 408d6a9..7ad726c 100644 --- a/src/query.zig +++ b/src/query.zig @@ -9,6 +9,7 @@ const _conditions = @import("conditions.zig"); const relations = @import("relations.zig"); const repository = @import("repository.zig"); const _comptime = @import("comptime.zig"); +const _result = @import("result.zig"); /// Repository query configuration structure. pub const RepositoryQueryConfiguration = struct { @@ -19,6 +20,8 @@ pub const RepositoryQueryConfiguration = struct { /// Compiled relations structure. const CompiledRelations = struct { + inlineRelations: []relations.ModelRelation, + otherRelations: []relations.ModelRelation, inlineSelect: []const u8, inlineJoins: []const u8, }; @@ -58,11 +61,15 @@ pub fn RepositoryQuery(comptime Model: type, comptime TableShape: type, comptime } break :compile CompiledRelations{ + .inlineRelations = inlineRelations, + .otherRelations = &[0]relations.ModelRelation{}, .inlineSelect = if (inlineSelect.len > 0) ", " ++ _comptime.join(", ", inlineSelect) else "", .inlineJoins = if (inlineJoins.len > 0) " " ++ _comptime.join(" ", inlineJoins) else "", }; } else { break :compile CompiledRelations{ + .inlineRelations = &[0]relations.ModelRelation{}, + .otherRelations = &[0]relations.ModelRelation{}, .inlineSelect = "", .inlineJoins = "", }; @@ -80,6 +87,9 @@ pub fn RepositoryQuery(comptime Model: type, comptime TableShape: type, comptime return struct { const Self = @This(); + /// Result mapper type. + pub const ResultMapper = _result.ResultMapper(Model, TableShape, repositoryConfig, compiledRelations.inlineRelations, compiledRelations.otherRelations); + arena: std.heap.ArenaAllocator, connector: database.Connector, connection: *database.Connection = undefined, @@ -286,7 +296,8 @@ pub fn RepositoryQuery(comptime Model: type, comptime TableShape: type, comptime defer queryResult.deinit(); // Map query results. - return postgresql.mapResults(Model, TableShape, repositoryConfig, allocator, queryResult); + var postgresqlReader = postgresql.QueryResultReader(TableShape, compiledRelations.inlineRelations).init(queryResult); + return try ResultMapper.map(allocator, postgresqlReader.reader()); } /// Initialize a new repository query. diff --git a/src/result.zig b/src/result.zig new file mode 100644 index 0000000..e90225e --- /dev/null +++ b/src/result.zig @@ -0,0 +1,72 @@ +const std = @import("std"); +const zollections = @import("zollections"); +const _repository = @import("repository.zig"); +const _relations = @import("relations.zig"); + +/// Generic interface of a query result reader. +pub fn QueryResultReader(comptime TableShape: type, comptime inlineRelations: ?[]const _relations.ModelRelation) type { + _ = inlineRelations; + return struct { + const Self = @This(); + + /// Generic interface of a query result reader instance. + pub const Instance = struct { + __interface: struct { + instance: *anyopaque, + next: *const fn (self: *anyopaque) anyerror!?TableShape, //TODO inline relations. + }, + + allocator: std.mem.Allocator, + + pub fn next(self: Instance) !?TableShape { + return self.__interface.next(self.__interface.instance); + } + }; + + _interface: struct { + instance: *anyopaque, + init: *const fn (self: *anyopaque, allocator: std.mem.Allocator) anyerror!Instance, + }, + + /// Initialize a reader instance. + pub fn init(self: Self, allocator: std.mem.Allocator) !Instance { + return self._interface.init(self._interface.instance, allocator); + } + }; +} + +/// 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 { + _ = relations; + return struct { + /// 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) { + // Create an arena for mapper data. + var mapperArena = std.heap.ArenaAllocator.init(allocator); + + // Initialize query result reader. + const reader = try queryReader.init(mapperArena.allocator()); + + // Initialize models list. + var models = std.ArrayList(*Model).init(allocator); + defer models.deinit(); + + // Get all raw models from the result reader. + while (try reader.next()) |rawModel| { + // Parse each raw model from the reader. + const model = try allocator.create(Model); + model.* = try repositoryConfig.fromSql(rawModel); + //TODO inline relations. + try models.append(model); + } + + //TODO load relations? + + // Return a result with the models. + return _repository.RepositoryResult(Model).init(allocator, + zollections.Collection(Model).init(allocator, try models.toOwnedSlice()), + mapperArena, + ); + } + }; +} diff --git a/src/update.zig b/src/update.zig index 920df26..153d366 100644 --- a/src/update.zig +++ b/src/update.zig @@ -7,6 +7,7 @@ const postgresql = @import("postgresql.zig"); const _sql = @import("sql.zig"); const conditions = @import("conditions.zig"); const repository = @import("repository.zig"); +const _result = @import("result.zig"); /// Repository update query configuration structure. pub fn RepositoryUpdateConfiguration(comptime UpdateShape: type) type { @@ -57,6 +58,9 @@ pub fn RepositoryUpdate(comptime Model: type, comptime TableShape: type, comptim const Configuration = RepositoryUpdateConfiguration(UpdateShape); + /// Result mapper type. + pub const ResultMapper = _result.ResultMapper(Model, TableShape, repositoryConfig, null, null); + arena: std.heap.ArenaAllocator, connector: database.Connector, connection: *database.Connection = undefined, @@ -301,7 +305,8 @@ pub fn RepositoryUpdate(comptime Model: type, comptime TableShape: type, comptim defer queryResult.deinit(); // Map query results. - return postgresql.mapResults(Model, TableShape, repositoryConfig, allocator, queryResult); + var postgresqlReader = postgresql.QueryResultReader(TableShape, null).init(queryResult); + return try ResultMapper.map(allocator, postgresqlReader.reader()); } /// Initialize a new repository update query.