Copyright (C) 1994, Digital Equipment Corp.
GENERIC MODULETheStableTbl (Tbl, Key, Value);
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----- StableTbl.T methods ---------------------------------------------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;
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); PROCEDUREThe following methods do not modify the table, so they are implemented by simply invoking the same method on the underlying table.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; PROCEDUREPut (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; PROCEDUREDelete (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;
PROCEDUREThe database methods are implemented by invoking the appropriate methods on the underlying database object.Get (t: T; READONLY k: Key.T; VAR v: Value.T): BOOLEAN = BEGIN RETURN t.tbl.get(k, v) END Get; PROCEDURESize (t: T): CARDINAL = BEGIN RETURN t.tbl.size() END Size; PROCEDUREIterate (t: T): Tbl.Iterator = BEGIN RETURN t.tbl.iterate() END Iterate;
PROCEDURE----- Closure methods -------------------------------------------------Checkpoint (t: T) RAISES { OSError.E } = BEGIN t.db.snapshot(t.tbl) END Checkpoint; PROCEDUREClose (t: T) RAISES { OSError.E } = BEGIN t.db.close() END Close; PROCEDURECheckpointSize (t: T): CARDINAL = BEGIN RETURN t.db.snapshotBytes() END CheckpointSize; PROCEDURELogSize (t: T): CARDINAL = BEGIN RETURN t.db.logBytes() END LogSize; PROCEDUREStatus (t: T): TEXT = BEGIN RETURN t.db.status() END Status;
First we define procedures used by several of the closure method implementations.
PROCEDUREThe closure methods themselves are implemented using Modula-3 Pickles.TextToAtomList (t: TEXT): AtomList.T = (* Returns a 1-element "AtomList.T" of the text "t". *) BEGIN RETURN AtomList.List1(Atom.FromText(t)) END TextToAtomList; PROCEDUREPickleRead (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; PROCEDUREPickleWrite (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;
PROCEDURENewTbl (cl: Closure): REFANY RAISES { } = BEGIN RETURN NEW(Tbl.Default).init(cl.sizeHint) END NewTbl; PROCEDURERecover (<*UNUSED*> cl: Closure; rd: Rd.T): REFANY RAISES { Rd.Failure } = VAR res: Tbl.Default := PickleRead(rd); BEGIN RETURN res END Recover; PROCEDURESnapshot (<*UNUSED*> cl: Closure; wr: Wr.T; r: REFANY) RAISES { Wr.Failure } = VAR t: Tbl.Default := r; BEGIN PickleWrite(wr, t); END Snapshot; PROCEDUREReadUpdate (<*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; PROCEDURELogUpdate (<*UNUSED*> cl: Closure; wr: Wr.T; r: REFANY) RAISES { Wr.Failure } = VAR update: Update := r; BEGIN PickleWrite(wr, update) END LogUpdate; BEGIN END StableTbl.