//
// DO NOT EDIT THIS FILE!
// THIS FILE IS AUTOMATICALLY GENERATED FROM A LITERATE PROGRAM SOURCE FILE.
//
//
// This software is copyrighted 2023 by G. Andrew Mangogna.
// The following terms apply to all files associated with the software unless
// explicitly disclaimed in individual files.
// 
// The author hereby grants permission to use, copy, modify, distribute,
// and license this software and its documentation for any purpose, provided
// that existing copyright notices are retained in all copies and that this
// notice is included verbatim in any distributions. No written agreement,
// license, or royalty fee is required for any of the authorized uses.
// Modifications to this software may be copyrighted by their authors and
// need not follow the licensing terms described here, provided that the
// new terms are clearly indicated on the first page of each file where
// they apply.
// 
// IN NO EVENT SHALL THE AUTHORS OR DISTRIBUTORS BE LIABLE TO ANY PARTY FOR
// DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING
// OUT OF THE USE OF THIS SOFTWARE, ITS DOCUMENTATION, OR ANY DERIVATIVES
// THEREOF, EVEN IF THE AUTHORS HAVE BEEN ADVISED OF THE POSSIBILITY OF
// SUCH DAMAGE.
// 
// THE AUTHORS AND DISTRIBUTORS SPECIFICALLY DISCLAIM ANY WARRANTIES,
// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT.  THIS SOFTWARE
// IS PROVIDED ON AN "AS IS" BASIS, AND THE AUTHORS AND DISTRIBUTORS HAVE
// NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS,
// OR MODIFICATIONS.
// 
// GOVERNMENT USE: If you are acquiring this software on behalf of the
// U.S. government, the Government shall have only "Restricted Rights"
// in the software and related documentation as defined in the Federal
// Acquisition Regulations (FARs) in Clause 52.227.19 (c) (2).  If you
// are acquiring the software on behalf of the Department of Defense,
// the software shall be classified as "Commercial Computer Software"
// and the Government shall have only "Restricted Rights" as defined in
// Clause 252.227-7013 (c) (1) of DFARs.  Notwithstanding the foregoing,
// the authors grant the U.S. Government and others acting in its behalf
// permission to use and distribute the software in accordance with the
// terms specified in this license.
//

const std = @import("std");
const meta = std.meta;
const math = std.math;
const log = std.log;
const assert = std.debug.assert;

const hai = @import("hashed_array_index.zig");
const rship = @import("rship_adj_matrix.zig");
const aral = @import("array_allocator.zig");

const ArrayAllocator = aral.ArrayAllocator;
const ElementId = aral.ElementId;
const HashedArrayIndex = hai.HashedArrayIndex;
const AssociationAdjMatrix = rship.AssociationAdjMatrix;

const CompareOperator = math.CompareOperator;
pub fn Relation(
    comptime T: type,
    comptime identifiers: []const []const u8,
    comptime capacity: usize,
) type {

    return struct {
        const Self = @This();
        const RelStorage = ArrayAllocator(T, capacity);
        const RelIndex = HashedArrayIndex(T, capacity, identifiers, 90);
        const IdentifierSet = std.StaticBitSet(@typeInfo(T).Struct.fields.len);

        storage: RelStorage = .{},
        index: RelIndex = .{},

        pub fn create(
            self: *Self,
            instance: T,
        ) !ElementId {
            const inst_id = try self.storage.create(instance);
            errdefer self.storage.free(inst_id) catch unreachable;
            self.storage.pool[inst_id.index] = instance;
            const inserted = self.index.insert(&self.storage.pool[inst_id.index]);
            return if(inserted) inst_id else error.DuplicateTuple;
        }
        pub fn createSet(
            self: *Self,
            population: []const T,
        ) !RelStorage.AllocationSet {
            var pop = RelStorage.AllocationSet.initEmpty();
            for (population) |instance| {
                const inst_id = try self.create(instance);
                pop.set(inst_id.index);
            }
            return pop;
        }
        pub fn delete(
            self: *Self,
            id: ElementId,
        ) !void {
            try self.storage.validateElementId(id);
            const inst_ref = self.index.remove(&self.storage.pool[id.index]);
            return if (inst_ref == null) error.ElementNotFound
                    else try self.storage.free(id);
        }
        pub fn deleteSet(
            self: *Self,
            del_set: RelStorage.AllocationSet,
        ) !void {
            var del_set_iter = self.storage.iterator(del_set);
            while (del_set_iter.next()) |inst_id| {
                try self.delete(inst_id);
            }
        }
        const identifier_set = makeIdentifierSet(identifiers);
        fn makeIdentifierSet(
            comptime id_attributes: []const []const u8,
        ) IdentifierSet {
            comptime var id_set = IdentifierSet.initEmpty();
        
            inline for (id_attributes) |id_attribute| {
                const id_field_index = meta.fieldIndex(T, id_attribute)
                        orelse @compileError("unknown identifier field: " ++ id_attribute);
                id_set.set(id_field_index);
            }
            return id_set;
        }
        fn validateIdAttribute(
            id_attribute: RelStorage.ElementField,
        ) !void {
            if (!identifier_set.isSet(@intFromEnum(id_attribute))) {
                return error.InvalidIdAttribute;
            }
        }
        pub fn selectByIdAttrs(
            self: *const Self, // N.B. receive by pointer since we take internal addresses.
            av_pairs: anytype,
        ) !ElementId {
            assert(av_pairs.len > 0);
        
            var key: T = undefined;
            inline for (av_pairs) |av_pair| {
                assert(av_pair.len == 2);
                const attr_field = av_pair.@"0";
                const attr_value = av_pair.@"1";
                try validateIdAttribute(attr_field);
                @field(key, RelStorage.elementFieldName(attr_field)) = attr_value;
            }
        
            const inst_ref = self.index.search(&key);
            return if (inst_ref) |found_ref| try self.storage.idFromRef(found_ref)
                    else error.ElementNotFound;
        }
        pub fn selectByAttrs(
            self: Self,
            av_pairs: anytype,
        ) !RelStorage.AllocationSet {
            var result = RelStorage.AllocationSet.initEmpty();
            var elem_iter = self.storage.iteratorAll();
            while (elem_iter.next()) |elem_id| {
                var match: bool = true;
                inline for (av_pairs) |av_pair| {
                    match = match and self.eqlAttribute(elem_id, av_pair.@"0", av_pair.@"1");
                }
                result.setValue(elem_id.index, match);
            }
        
            return result;
        }
        pub fn readInstance(
            self: Self,
            inst_id: ElementId,
        ) !T {
            return self.storage.read(inst_id);
        }
        pub fn readAttribute(
            self: Self,
            inst_id: ElementId,
            comptime attribute: RelStorage.ElementField,
        ) !RelStorage.ElementFieldType(attribute) {
            return self.storage.readField(inst_id, attribute);
        }
        pub fn updateAttribute(
            self: *Self,
            inst_id: ElementId,
            comptime attribute: RelStorage.ElementField,
            value: RelStorage.ElementFieldType(attribute),
        ) !void {
            return self.storage.updateField(inst_id, attribute, value);
        }
        pub fn compareAttribute(
            self: Self,
            inst_id: ElementId,
            comptime attribute: RelStorage.ElementField,
            op: CompareOperator,
            value: RelStorage.ElementFieldType(attribute),
        ) bool {
            return if (self.readAttribute(inst_id, attribute)) |attr_value|
                math.compare(attr_value, op, value)
            else |_| false;
        }
        pub fn eqlAttribute(
            self: Self,
            inst_id: ElementId,
            comptime attribute: RelStorage.ElementField,
            value: RelStorage.ElementFieldType(attribute),
        ) bool {
            return if (self.readAttribute(inst_id, attribute)) |attr_value|
                meta.eql(attr_value, value)
            else |_| false;
        }
    };
}
const SupplierNumber = []const u8;
const PartNumber = []const u8;
const Name = []const u8;
const Status = usize;
const Qty = usize;
const Weight = f32;
const PartColor = enum {
    Red,
    Blue,
    Green,
};
const Supplier = struct {
    s_num: SupplierNumber,      // identifier
    s_name: Name,
    s_status: Status,
    s_city: Name,

    pub fn format(
        value: @This(),
        comptime _: []const u8,
        _: std.fmt.FormatOptions,
        writer: anytype,
    ) !void {
        _ = try writer.print("| {s: <3}| {s: <6}| {d: <3}| {s: <7}|", value);
    }
};
const suppliers = [_]Supplier {
    .{.s_num = "S1", .s_name = "Smith", .s_status = 20, .s_city = "London"},    // 0
    .{.s_num = "S2", .s_name = "Jones", .s_status = 10, .s_city = "Paris"},     // 1
    .{.s_num = "S3", .s_name = "Blake", .s_status = 30, .s_city = "Paris"},     // 2
    .{.s_num = "S4", .s_name = "Clark", .s_status = 20, .s_city = "London"},    // 3
    .{.s_num = "S5", .s_name = "Adams", .s_status = 30, .s_city = "Athens"},    // 4
};
const supplier_capacity: usize = 10;
const SupplierRelation = Relation(Supplier, &[_][]const u8{"s_num"}, supplier_capacity);
var supplier_relvar: SupplierRelation = .{};
const Part = struct {
    p_num: PartNumber,          // identifier
    p_name: Name,
    p_color: PartColor,
    p_weight: Weight,
    p_city: Name,

    pub fn format(
        value: @This(),
        comptime _: []const u8,
        _: std.fmt.FormatOptions,
        writer: anytype,
    ) !void {
        _ = try writer.print("| {s: <3}| {s: <6}| {s: <6}| {d: <5.1}| {s: <7}|",
            .{value.p_num, value.p_name, @tagName(value.p_color), value.p_weight, value.p_city});
    }
};
const parts = [_]Part {
    .{.p_num = "P1", .p_name = "Nut", .p_color = .Red, .p_weight = 12.0,
            .p_city = "London"},  // 0
    .{.p_num = "P2", .p_name = "Bolt", .p_color = .Green, .p_weight = 17.0,
            .p_city = "Paris"},// 1
    .{.p_num = "P3", .p_name = "Screw", .p_color = .Blue, .p_weight = 17.0,
            .p_city = "Oslo"}, // 2
    .{.p_num = "P4", .p_name = "Screw", .p_color = .Red, .p_weight = 14.0,
            .p_city = "London"},// 3
    .{.p_num = "P5", .p_name = "Cam", .p_color = .Blue, .p_weight = 12.0,
            .p_city = "Paris"},  // 4
    .{.p_num = "P6", .p_name = "Cog", .p_color = .Red, .p_weight = 19.0,
            .p_city = "London"},  // 5
};
const part_capacity: usize = 10;
const PartRelation = Relation(Part, &[_][]const u8{"p_num"}, part_capacity);
var part_relvar: PartRelation = .{};
const SuppliedPart = struct {
    s_num: SupplierNumber,      // identifier
    p_num: PartNumber,          // identifier
    qty: Qty,

    pub fn format(
        value: @This(),
        comptime _: []const u8,
        _: std.fmt.FormatOptions,
        writer: anytype,
    ) !void {
        _ = try writer.print("| {s: <3}| {s: <3}| {d: <5}|", value);
    }
};
const supplied_parts = [_]SuppliedPart {
    .{.s_num = "S1", .p_num = "P1", .qty = 300,},   // 0
    .{.s_num = "S1", .p_num = "P2", .qty = 200,},   // 1
    .{.s_num = "S1", .p_num = "P3", .qty = 400,},   // 2
    .{.s_num = "S1", .p_num = "P4", .qty = 200,},   // 3
    .{.s_num = "S1", .p_num = "P5", .qty = 100,},   // 4
    .{.s_num = "S1", .p_num = "P6", .qty = 100,},   // 5
    .{.s_num = "S2", .p_num = "P1", .qty = 300,},   // 6
    .{.s_num = "S2", .p_num = "P2", .qty = 400,},   // 7
    .{.s_num = "S3", .p_num = "P2", .qty = 200,},   // 8
    .{.s_num = "S4", .p_num = "P2", .qty = 200,},   // 9
    .{.s_num = "S4", .p_num = "P4", .qty = 300,},   // 10
    .{.s_num = "S4", .p_num = "P5", .qty = 400,},   // 11
};
const supplied_part_capacity: aral.AllocCount = 50;
const SuppliedPartRelation = Relation(SuppliedPart, &[_][]const u8{"s_num", "p_num"},
        supplied_part_capacity);
var supplied_part_relvar: SuppliedPartRelation = .{};
const SuppliersToPartsAssociation = struct {
    // Use two adjacency matrices to handle the decomposition.
    // Supplied Part is the domain in both cases.
    pub const SuppliedPartToSupplier =
        AssociationAdjMatrix(supplied_part_capacity, .any, .one, supplier_capacity);
    pub const SuppliedPartToPart =
        AssociationAdjMatrix(supplied_part_capacity, .at_least_one, .one, part_capacity);

    sp_to_s: SuppliedPartToSupplier = SuppliedPartToSupplier.init(),
    sp_to_p: SuppliedPartToPart = SuppliedPartToPart.init(),

    const Self = @This();

    // To navigate from Supplier to Supplied Part we start with a
    // subset of the codomain of R1a, i.e. a subset of Supplier.
    // The preimage under the Supplied Part to Supplier adjacency matrix
    // is the set of Supplied Parts associated with the Supplier subset.
    fn navigateSupplierToSuppliedPart(
        self: Self,
        s_inst_set: SuppliedPartToSupplier.CodomainSet
    ) SuppliedPartToSupplier.DomainSet {
        return self.sp_to_s.preimage(s_inst_set);
    }

    // In the other direction from Part to Supplied Part, we start with
    // a subset of the codomain of R1b, i.e. a subset of Part.
    // The preimage under the Supplied Part to Part adjacency matrix
    // is the set of Supplied Parts associated with the Part subset.
    fn navigatePartToSuppliedPart(
        self: Self,
        p_inst_set: SuppliedPartToPart.CodomainSet
    ) SuppliedPartToPart.DomainSet {
        return self.sp_to_p.preimage(p_inst_set);
    }

    // Two more functions are used to navigate from Supplied Part to
    // either Supplier are Part.
    fn navigateSuppliedPartToSupplier(
        self: Self,
        sp_inst_set: SuppliedPartToSupplier.DomainSet
    ) SuppliedPartToSupplier.CodomainSet {
        return self.sp_to_s.image(sp_inst_set);
    }

    fn navigateSuppliedPartToPart(
        self: Self,
        sp_inst_set: SuppliedPartToPart.DomainSet
    ) SuppliedPartToPart.CodomainSet {
        return self.sp_to_p.image(sp_inst_set);
    }

    // To navigate both decomposed association from Supplier to Part,
    // the result is a lovely composition of navigating each of the
    // decomposed associations. Closure and composition properties are amazing.
    fn navigateSupplierToPart(
        self: Self,
        s_inst_set: SuppliedPartToSupplier.CodomainSet,
    ) SuppliedPartToPart.CodomainSet {
        return self.navigateSuppliedPartToPart(
                self.navigateSupplierToSuppliedPart(s_inst_set));
    }

    // The navigation from Part to Supplier is just the reverse composition.
    fn navigatePartToSupplier(
        self: Self,
        p_inst_set: SuppliedPartToPart.CodomainSet,
    ) SuppliedPartToSupplier.CodomainSet {
        return self.navigateSuppliedPartToSupplier(
                self.navigatePartToSuppliedPart(p_inst_set));
    }

    // Combine the decomposed relations into a single consistency check.
    fn isAssociationConsistent(
        self: Self,
        sp_universe: SuppliedPartToPart.DomainSet,
        s_universe: SuppliedPartToSupplier.CodomainSet,
        p_universe: SuppliedPartToPart.CodomainSet,
    ) bool {
        return self.sp_to_s.isAssociationConsistent(sp_universe, s_universe)
            and self.sp_to_p.isAssociationConsistent(sp_universe, p_universe);
    }

    // Find any unrelated Part instances
    fn partsUnrelatedToSupplier(
        self: Self,
        supplier_universe: SuppliedPartToSupplier.CodomainSet,
        part_universe: SuppliedPartToPart.CodomainSet,
    ) SuppliedPartToSupplier.CodomainSet {
        return self.sp_to_p.imageComplement(
            self.navigateSupplierToSuppliedPart(supplier_universe),
            part_universe);
    }

    // In the previous presentation of this example, the association was realized
    // by a set of constant pairs given at compile time.  We can do better here
    // since all the information needed to compute the initial association pairing
    // is contained in the values of the initial population.
    fn associate(
        self: *Self,
        sp_class: *const SuppliedPartRelation,
        s_class: *const SupplierRelation,
        p_class: *const PartRelation,
    ) !void {
        var sp_iter = sp_class.storage.iteratorAll();
        while (sp_iter.next()) |sp_id| {
            const sp_s_num = try sp_class.readAttribute(sp_id, .s_num);
            const supplier_id = try s_class.selectByIdAttrs(.{          // <1>
                .{ .s_num, sp_s_num},
            });
            self.sp_to_s.relate(sp_id.index, supplier_id.index);

            const sp_p_num = try sp_class.readAttribute(sp_id, .p_num);
            const part_id = try p_class.selectByIdAttrs(.{
                .{ .p_num, sp_p_num},
            });
            self.sp_to_p.relate(sp_id.index, part_id.index);
        }
    }
};

var suppliers_to_parts_assoc: SuppliersToPartsAssociation = .{};
pub fn main() !void {
    _ = try supplier_relvar.createSet(&suppliers);
    _ = try part_relvar.createSet(&parts);
    _ = try supplied_part_relvar.createSet(&supplied_parts);
    try suppliers_to_parts_assoc.associate(&supplied_part_relvar, &supplier_relvar, &part_relvar);
    var consistent = suppliers_to_parts_assoc.isAssociationConsistent(
            supplied_part_relvar.storage.allocations,
            supplier_relvar.storage.allocations,
            part_relvar.storage.allocations);
    assert(consistent);
    log.info("==========================", .{});
    // First question.
    log.info("1. What parts are supplied by Smith?", .{});
    
    // Since the supplier name is not an identifier, we must iterate over the
    // suppliers and build a bit set for those Suppliers named, Smith.
    // The implication of the model is that there may be many distinct Suppliers
    // whose name is Smith. That may or may not meet the requirements of the
    // application, but it is what the model implies.
    
    var smith_set = try supplier_relvar.selectByAttrs(.{
        .{ .s_name, "Smith" },
    });
    const smith_parts = suppliers_to_parts_assoc.navigateSupplierToPart(smith_set);
    var parts_iter = part_relvar.storage.iterator(smith_parts);
    while (parts_iter.next()) |part_id| {
        const part_inst = try part_relvar.readInstance(part_id);
        log.info("  {}", .{part_inst});
    }
    // Second question.
    log.info("2. What parts are supplied by Suppliers located in Paris?", .{}) ;
    
    // Again, we construct a sequential search of the Supplier class
    // looking for those Suppliers located in Paris.
    
    var paris_suppliers = try supplier_relvar.selectByAttrs(.{
        .{ .s_city, "Paris" },
    });
    const paris_parts = suppliers_to_parts_assoc.navigateSupplierToPart(paris_suppliers);
    parts_iter = part_relvar.storage.iterator(paris_parts);
    while (parts_iter.next()) |part_id| {
        const part_inst = try part_relvar.readInstance(part_id);
        log.info("  {}", .{part_inst});
    }
    // Third question.
    log.info("3. What is the quantity of all parts supplied by Jones?", .{}) ;
    
    var jones_set = try supplier_relvar.selectByAttrs(.{
        .{ .s_name, "Jones" },
    });
    const jones_supplied_parts =
        suppliers_to_parts_assoc.navigateSupplierToSuppliedPart(jones_set);
    
    // With the supplied parts that Jones supplies, we can add up the QTY values.
    var quantity: usize = 0;
    var supplied_parts_iter = supplied_part_relvar.storage.iterator(jones_supplied_parts);
    while (supplied_parts_iter.next()) |supplied_part_id| {
        quantity += try supplied_part_relvar.readAttribute(supplied_part_id, .qty);
    }
    
    log.info("  Jones supplies {d} parts", .{quantity});
    // Fourth question.
    log.info("4. Which suppliers supply Red parts?", .{});
    
    // For this case, we also perform a sequential search for the Red parts.
    var red_parts = try part_relvar.selectByAttrs(.{
        .{ .p_color, .Red },
    });
    const red_part_suppliers = suppliers_to_parts_assoc.navigatePartToSupplier(red_parts);
    var red_supplier_iter = supplier_relvar.storage.iterator(red_part_suppliers);
    while (red_supplier_iter.next()) |supplier_id| {
        const supplier_inst = try supplier_relvar.readInstance(supplier_id);
        log.info("  {}", .{supplier_inst});
    }
    // Fifth question.
    log.info("5. Which suppliers supply parts that are co-located with the supplier?", .{});
    
    // In relational algebraic terms, this query can be satisfied by a semijoin
    // operation. If the Supplier and Part relations are joined across Supplier.CITY
    // and Part.CITY and then the attributes of Supplier are projected from the
    // result, we obtain the Supplier tuples of interest.
    // We're going to simulate that algebra using simple brute force iteration and
    // comparison which works fine for a small population.
    
    // The strategy used here is to iterate across the supplier tuples and for each tuple
    // compute the related parts. For each related part, we compare the CITY attribute
    // values, adding any match to an accumulating bit set of Supplier.
    
    // The result is a set of suppliers that are co-located with the parts they supply.
    var colocated_supplier =
            SuppliersToPartsAssociation.SuppliedPartToSupplier.emptyCodomainSet;
    
    var supplier_iter = supplier_relvar.storage.iteratorAll();
    while (supplier_iter.next()) |supplier_id| {
        var supplier_set = SuppliersToPartsAssociation.SuppliedPartToSupplier.
                makeSingularCodomainSet(supplier_id.index);
        var a_suppliers_parts = suppliers_to_parts_assoc.navigateSupplierToPart(supplier_set);
    
        const supplier_city = try supplier_relvar.readAttribute(supplier_id, .s_city);
        parts_iter = part_relvar.storage.iterator(a_suppliers_parts);
        while (parts_iter.next()) |part_id| {
            if (part_relvar.eqlAttribute(part_id, .p_city, supplier_city)) {
                colocated_supplier.set(supplier_id.index);
            }
        }
    }
    
    var colocated_iter = supplier_relvar.storage.iterator(colocated_supplier);
    while (colocated_iter.next()) |supplier_id| {
        const supplier_inst = try supplier_relvar.readInstance(supplier_id);
        log.info("  {}", .{supplier_inst});
    }
    // Sixth question.
    log.info("6. Which suppliers supply no parts?", .{});
    
    // Here we want the complement of the image of supplied parts to supplier.
    var no_part_suppliers = suppliers_to_parts_assoc.sp_to_s.imageComplement(
            supplied_part_relvar.storage.allocations, supplier_relvar.storage.allocations);
    var no_part_iter = supplier_relvar.storage.iterator(no_part_suppliers);
    while (no_part_iter.next()) |supplier_id| {
        const supplier_inst = try supplier_relvar.readInstance(supplier_id);
        log.info("  {}", .{supplier_inst});
    }
    // Seventh question.
    log.info("7. Which parts have a weight greater than 15.0?", .{});
    var heavy_parts = PartRelation.RelStorage.AllocationSet.initEmpty();
    var all_parts_iter = part_relvar.storage.iteratorAll();
    while (all_parts_iter.next()) |a_part_id| {
        if (part_relvar.compareAttribute(a_part_id, .p_weight, .gt, 15.0)) {
            heavy_parts.set(a_part_id.index);
        }
    }
    
    var heavy_iter = part_relvar.storage.iterator(heavy_parts);
    while (heavy_iter.next()) |heavy_id| {
        const heavy_inst = try part_relvar.readInstance(heavy_id);
        log.info("  {}", .{heavy_inst});
    }
    // Eighth question.
    log.info("8. Supplier, S5 (Adams), now supplies 100 P2 (Bolt) parts", .{});
    
    const supplier_id = try supplier_relvar.selectByIdAttrs(.{
        .{ .s_num, "S5" },
    });
    const part_id = try part_relvar.selectByIdAttrs(.{
        .{ .p_num, "P2" },
    });
    
    const sp_id = try supplied_part_relvar.create(
            .{ .s_num = "S5", .p_num = "P2", .qty = 100, });
    suppliers_to_parts_assoc.sp_to_s.relate(sp_id.index, supplier_id.index);
    suppliers_to_parts_assoc.sp_to_p.relate(sp_id.index, part_id.index);
    
    consistent = suppliers_to_parts_assoc.isAssociationConsistent(
            supplied_part_relvar.storage.allocations,
            supplier_relvar.storage.allocations,
            part_relvar.storage.allocations);
    assert(consistent);
    
    const sp_inst = try supplied_part_relvar.readInstance(sp_id);
    log.info("  Created Supplied Part part:", .{});
    log.info("  {}", .{sp_inst});
    // Ninth question.
    log.info("9. Supplier, S1 (Smith), no longer supplies part, P3 (Screw)", .{});
    
    // If a Supplier no longer supplies a part, this implies that we must:
    // (a) delete an instance of Supplied Part, and
    // (b) take any compensatory actions required by the Supplied Part deletion.
    // In this case, since the association is from Supplier to Part is unconditional
    // on the Supplier side, if there are no other suppliers supplying the Part
    // then it is necessary to delete the instance of Part to maintain referential
    // consistency.
    
    const sp_s1_p3_id = try supplied_part_relvar.selectByIdAttrs(.{
        .{ .s_num, "S1" },
        .{ .p_num, "P3" },
    });
    suppliers_to_parts_assoc.sp_to_p.unrelateDomainElement(sp_s1_p3_id.index);
    suppliers_to_parts_assoc.sp_to_s.unrelateDomainElement(sp_s1_p3_id.index);
    
    log.info("  Deleting Supplied Part part:", .{});
    const sp_s1_s3_inst = try supplied_part_relvar.readInstance(sp_s1_p3_id);
    log.info("  {}", .{sp_s1_s3_inst});
    try supplied_part_relvar.delete(sp_s1_p3_id);
    
    // The complement of the image of traversing from Part to Supplier gives
    // the set of all Part that are not related to any Supplier.
    // Those instance(s) of Part must be deleted to restore referential consistency.
    const unrelated_parts = suppliers_to_parts_assoc.partsUnrelatedToSupplier(
        supplier_relvar.storage.allocations, part_relvar.storage.allocations);
    
    // This loop is used just to print out the parts that are to be deleted.
    var unrelated_iter = part_relvar.storage.iterator(unrelated_parts);
    while (unrelated_iter.next()) |unrelated_part_id| {
        log.info("  Deleting unrelated part:", .{});
        const part_inst = try part_relvar.readInstance(unrelated_part_id);
        log.info("  {}", .{part_inst});
    }
    // Delete the parts as a set.
    try part_relvar.deleteSet(unrelated_parts);
    
    consistent = suppliers_to_parts_assoc.isAssociationConsistent(
            supplied_part_relvar.storage.allocations,
            supplier_relvar.storage.allocations,
            part_relvar.storage.allocations);
    assert(consistent);
    // Tenth question.
    log.info("10. Add 5 to the status of all suppliers located in London", .{});
    
    // This question is a simple variation of previous calculations.
    // The strategy is to compute the set of London supplier and
    // iterate over that set. For each of the suppliers, the status
    // attribute is read, modified, and updated.
    
    var london_suppliers = try supplier_relvar.selectByAttrs(.{
        .{ .s_city, "London" },
    });
    var london_iter = supplier_relvar.storage.iterator(london_suppliers);
    while (london_iter.next()) |london_supplier_id| {
        var status = try supplier_relvar.readAttribute(london_supplier_id, .s_status);
        status += 5;
        try supplier_relvar.updateAttribute(london_supplier_id, .s_status, status);
    }
    london_iter = supplier_relvar.storage.iterator(london_suppliers);
    while (london_iter.next()) |london_supplier_id| {
        const london_supplier = try supplier_relvar.readInstance(london_supplier_id);
        log.info("  {}", .{london_supplier});
    }
    log.info("==========================", .{});
}
