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 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); 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.