stabletable/src/StableTbl.mg


Copyright (C) 1994, Digital Equipment Corp.

GENERIC MODULE StableTbl(Tbl, Key, Value);
The Tbl, Key, and Value interfaces should the same as those passed to the generic StableTbl interface.

IMPORT SmallDB, Rd, Wr, Pickle, OSError, Atom, AtomList;

IMPORT Thread;
<* FATAL Thread.Alerted *>
We define contant TEXT brands for each of the types written to pickles so that unpickling will be reliable.

CONST
  TBrand = "(Stable " & Tbl.Brand & ")";
  ClearBrand = "(Clear " & TBrand & ")";
  AddBrand = "(Add " & TBrand & ")";
  DeleteBrand = "(Delete " & TBrand & ")";
The type StableTbl.T has an embedded Tbl.Default (the real table where entries are stored) and an instance of a SmallDB.T for this instance of the table.

REVEAL
  T = Public BRANDED TBrand OBJECT
    tbl: Tbl.Default;
    db: SmallDB.T;
  OVERRIDES
    init := Init;
    get := Get;
    put := Put;
    delete := Delete;
    size := Size;
    iterate := Iterate;
    checkpoint := Checkpoint;
    close := Close;
    checkpointSize := CheckpointSize;
    logSize := LogSize;
    status := Status;
  END;
We define our own SmallDB.Closure object for creating new Tbl.Default objects, reading and writing them from/to disk, and for reading and writing table updates from/to disk. The closure has a sizeHint field so the client's sizeHint can be respected if the underlying table is being created for the first time. The closure's sizeHint is only used by the implementation of the new method.

TYPE
  Closure = SmallDB.Closure OBJECT
    (* for use by "NewTbl" *)
    sizeHint: CARDINAL;
  OVERRIDES
    new := NewTbl;
    recover := Recover;
    snapshot := Snapshot;
    readUpdate := ReadUpdate;
    logUpdate := LogUpdate;
  END;
We also define object for the various types of table updates. The three specific update types are declared to be subtypes of a base Update type.

TYPE
  Update = ROOT OBJECT END;
  ClearUpdate = Update BRANDED ClearBrand OBJECT
    sizeHint: CARDINAL;
  END;
  AddUpdate = Update BRANDED AddBrand OBJECT
    key: Key.T;
    value: Value.T;
  END;
  DeleteUpdate = Update BRANDED DeleteBrand OBJECT
    key: Key.T;
  END;

PROCEDURE New(dir: TEXT; sizeHint: CARDINAL): T
    RAISES { OSError.E, SmallDB.Failed } =
  VAR res := NEW(T); BEGIN
    (* first create the database for this table *)
    VAR cl := NEW(Closure, sizeHint := sizeHint); BEGIN
      res.db := SmallDB.New(dir, cl, pad := FALSE)
    END;

    (* then recover the "Tbl.Default" from the database *)
    res.tbl := res.db.recover();
    RETURN res
  END New;
----- StableTbl.T methods ---------------------------------------------

We create one global instance of each update type; these are re-used by the methods that write updates to the database.

VAR
  clear := NEW(ClearUpdate);
  add := NEW(AddUpdate);
  delete := NEW(DeleteUpdate);

PROCEDURE Init(t: T; sizeHint: CARDINAL): T
    RAISES { OSError.E } =
  BEGIN
    (* log the update *)
    clear.sizeHint := sizeHint;
    t.db.update(clear);

    (* invoke the method on the underlying table *)
    EVAL t.tbl.init(sizeHint);
    RETURN t
  END Init;

PROCEDURE Put(t: T; READONLY k: Key.T; READONLY v: Value.T): BOOLEAN
    RAISES { OSError.E } =
  BEGIN
    (* log the update *)
    add.key := k; add.value := v;
    t.db.update(add);

    (* invoke the method on the underlying table *)
    RETURN t.tbl.put(k, v)
  END Put;

PROCEDURE Delete(t: T; READONLY k: Key.T; VAR v: Value.T): BOOLEAN
    RAISES { OSError.E } =
  BEGIN
    (* log the update *)
    delete.key := k;
    t.db.update(delete);

    (* invoke the method on the underlying table *)
    RETURN t.tbl.delete(k, (*OUT*) v)
  END Delete;
The following methods do not modify the table, so they are implemented by simply invoking the same method on the underlying table.

PROCEDURE Get(t: T; READONLY k: Key.T; VAR v: Value.T): BOOLEAN =
  BEGIN RETURN t.tbl.get(k, v) END Get;

PROCEDURE Size(t: T): CARDINAL =
  BEGIN RETURN t.tbl.size() END Size;

PROCEDURE Iterate(t: T): Tbl.Iterator =
  BEGIN RETURN t.tbl.iterate() END Iterate;
The database methods are implemented by invoking the appropriate methods on the underlying database object.

PROCEDURE Checkpoint(t: T) RAISES { OSError.E } =
  BEGIN t.db.snapshot(t.tbl) END Checkpoint;

PROCEDURE Close(t: T) RAISES { OSError.E } =
  BEGIN t.db.close() END Close;

PROCEDURE CheckpointSize(t: T): CARDINAL =
  BEGIN RETURN t.db.snapshotBytes() END CheckpointSize;

PROCEDURE LogSize(t: T): CARDINAL =
  BEGIN RETURN t.db.logBytes() END LogSize;

PROCEDURE Status(t: T): TEXT =
  BEGIN RETURN t.db.status() END Status;
----- Closure methods -------------------------------------------------

First we define procedures used by several of the closure method implementations.

PROCEDURE TextToAtomList(t: TEXT): AtomList.T =
  (* Returns a 1-element "AtomList.T" of the text "t". *)
  BEGIN RETURN AtomList.List1(Atom.FromText(t)) END TextToAtomList;

PROCEDURE PickleRead(rd: Rd.T): REFANY
    RAISES { Rd.Failure } =
  (* Like "Pickle.Read(rd)", but converts "Pickle.Error" and
     "Rd.EndOfFile" exceptions to a "Rd.Failure" exception. *)
  VAR res: REFANY; BEGIN
    TRY res := Pickle.Read(rd) EXCEPT
    | Pickle.Error (msg) => RAISE Rd.Failure(TextToAtomList(msg))
    | Rd.EndOfFile => RAISE Rd.Failure(TextToAtomList("premature EOF"))
    END;
    RETURN res
  END PickleRead;

PROCEDURE PickleWrite(wr: Wr.T; r: REFANY)
    RAISES { Wr.Failure } =
  (* Like "Pickle.Write(wr, r)", but converts the "Pickle.Error"
     exception to a "Wr.Failure" exception. *)
  BEGIN
    TRY Pickle.Write(wr, r) EXCEPT
    | Pickle.Error (msg) => RAISE Wr.Failure(TextToAtomList(msg))
    END;
  END PickleWrite;
The closure methods themselves are implemented using Modula-3 Pickles.

PROCEDURE NewTbl(cl: Closure): REFANY RAISES { } =
  BEGIN RETURN NEW(Tbl.Default).init(cl.sizeHint) END NewTbl;

PROCEDURE Recover(<*UNUSED*> cl: Closure; rd: Rd.T): REFANY
    RAISES { Rd.Failure } =
  VAR res: Tbl.Default := PickleRead(rd); BEGIN
    RETURN res
  END Recover;

PROCEDURE Snapshot(<*UNUSED*> cl: Closure; wr: Wr.T; r: REFANY)
    RAISES { Wr.Failure } =
  VAR t: Tbl.Default := r; BEGIN
    PickleWrite(wr, t);
  END Snapshot;

PROCEDURE ReadUpdate(<*UNUSED*> cl: Closure; rd: Rd.T;
    state: REFANY): REFANY RAISES { Rd.Failure } =
  VAR
    (* read the update *)
    update: Update := PickleRead(rd);
    t: Tbl.Default := state;
  BEGIN
    (* apply the update based on its type *)
    TYPECASE update OF <* NOWARN *>
    | NULL => <* ASSERT FALSE *>
    | ClearUpdate (clr) =>
        EVAL t.init(clr.sizeHint)
    | AddUpdate (add) =>
        EVAL t.put(add.key, add.value)
    | DeleteUpdate (del) =>
        VAR value: Value.T; BEGIN
          EVAL t.delete(del.key, (*OUT*) value)
        END
    END;
    RETURN t
  END ReadUpdate;

PROCEDURE LogUpdate(<*UNUSED*> cl: Closure; wr: Wr.T;
    r: REFANY) RAISES { Wr.Failure } =
  VAR update: Update := r; BEGIN
    PickleWrite(wr, update)
  END LogUpdate;

BEGIN
END StableTbl.