aboutsummaryrefslogtreecommitdiffstats
path: root/lib/kernel/EphemeralRelation.ml
blob: 3fb8e72c5040202322f774db5f1411b8e2eeb1a5 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
module type DERIVATION = sig
  type plan

  val identity : plan -> Concepts.Hash.hash

  val describe :
    plan -> (Protocols.Schematics.relation_description, Concepts.Condition.condition) result

  val modes : plan -> (Concepts.Mode.t, Concepts.Condition.condition) result

  val generate :
    plan ->
    Protocols.Generative.binding ->
    (Protocols.Handle.t, Concepts.Condition.condition) result
end

module Make (D : DERIVATION) = struct
  module Error = struct
    open Concepts.Condition

    let attributes bound = Concepts.Value.String (String.concat ", " (BatSet.String.elements bound))

    let ungenerable bound =
      condition "ungenerable-binding"
        "The derivation declares no mode that generates under the attributes bound."
        ("bound" |=| attributes bound)

    let undecidable bound =
      condition "undecidable-membership"
        "The derivation declares no mode that decides membership under the attributes of the tuple."
        ("bound" |=| attributes bound)
  end

  let pinned_tag = "rnt/pinned/1"

  let identity ?pinned plan =
    let derivation = D.identity plan in
    match pinned with
    | None -> derivation
    | Some branch ->
        pinned_tag ^ Concepts.Hash.to_raw_string derivation ^ Concepts.Hash.to_raw_string branch
        |> Bytes.of_string
        |> Concepts.Hash.hash_of_bytes

  let exhaustible declaration bound =
    match Concepts.Mode.generation declaration bound with
    | Some cardinality -> Concepts.Cardinality.exhaustible cardinality
    | None -> false

  let decides declaration bound =
    Concepts.Mode.decision declaration bound || exhaustible declaration bound

  (* A cursor holds a reference to its relation, so the edges it reads
     outlive a release of the relation that produced it. *)
  class retaining scan release_owner =
    object (self)
      inherit Lifecycle.counted
      inherit Identity.of_id

      method destroy =
        Protocols.Handle.release (Protocols.Handle.from scan);
        release_owner ()

      method fetch limit = Protocols.Cursor.fetch scan limit
      method protocols : Protocols.Handle.protocol list = [Protocols.Cursor.make self]
    end

  class ephemeral plan address declaration total edges =
    object (self)
      inherit Lifecycle.counted
      val plan : D.plan = plan
      method destroy = List.iter Protocols.Handle.release edges
      method predicate = Ok None
      method local_constraints = Ok None
      method modes = Ok declaration

      method private retained binding =
        let open Utilities.Result in
        let bound = Protocols.Generative.bound binding in
        let* () =
          if Option.is_some (Concepts.Mode.generation declaration bound) then Ok ()
          else Error (Error.ungenerable bound)
        in
        let* cursor = D.generate plan binding in
        match Protocols.Cursor.require cursor with
        | Error c ->
            Protocols.Handle.release cursor;
            Error c
        | Ok scan ->
            ignore self#reference;
            Ok (new retaining scan (fun () -> self#release) |> Protocols.Handle.make)

      method generate binding = self#retained binding
      method enumerate = self#retained Protocols.Generative.nothing

      method describe () =
        D.describe plan |> Result.map (fun description -> Protocols.Schematics.Relation description)

      (* Membership of a derived relation is decided by the derivation
         and not by a lookup, so it is asked of the generator under the
         binding the tuple itself fixes. *)
      method contains (tuple : Concepts.Tuple.t) =
        let open Utilities.Result in
        let binding = tuple.Concepts.Tuple.attributes in
        let bound = Protocols.Generative.bound binding in
        let* () = if decides declaration bound then Ok () else Error (Error.undecidable bound) in
        let* cursor = D.generate plan binding in
        Fun.protect
          ~finally:(fun () -> Protocols.Handle.release cursor)
          (fun () ->
            let* scan = Protocols.Cursor.require cursor in
            let* first = Protocols.Cursor.next scan in
            Ok (Option.is_some first) )

      method protocols : Protocols.Handle.protocol list =
        let carried =
          [ Protocols.Relation.make self;
            Protocols.Schematics.make self;
            Protocols.Generative.make self ]
        in
        if total then Protocols.Enumerable.make self :: carried else carried

      method hash : Concepts.Hash.hash = address
    end

  let derive ?pinned ?(edges = []) plan =
    let open Utilities.Result in
    let* declaration = D.modes plan in
    Ok
      ( new ephemeral plan (identity ?pinned plan) declaration (exhaustible declaration BatSet.String.empty) edges
      |> Protocols.Handle.make )
end