DIRECTORY
FS USING [ComponentPositions, ExpandName],
LoganBerry,
LoganBerryExtras,
LoganBerryRpcControl,
RefID USING [Seal, Unseal],
Rope USING [Equal, ROPE, Substr, Replace],
RPC USING [ShortROPE, VersionRange, matchAllVersions, SecurityLevel, Conversation, EncryptionKey, MakeKey, StartConversation, AuthenticateFailed, AuthenticateFailure, CallFailed, CallFailure, ImportFailed, ImportFailure],
UserCredentials USING [Get],
LoganBerryStubExtras,
LoganBerryStub;
~
BEGIN
OPEN LoganBerryStub;
ROPE: TYPE = Rope.ROPE;
loganBerryType: RPC.ShortROPE = "LoganBerry.Lark";
loganBerryVersion: RPC.VersionRange = RPC.matchAllVersions;
securityLevel: RPC.SecurityLevel = authOnly;
InstanceInterface: TYPE = REF InstanceInterfaceRecord;
InstanceInterfaceRecord:
TYPE =
RECORD [
instance: RPC.ShortROPE,
interface: LoganBerryRpcControl.InterfaceRecord,
conv: RPC.Conversation
];
interfaceCache: LIST OF InstanceInterface;
DBInterface: TYPE = REF DBInterfaceRecord;
DBInterfaceRecord:
TYPE =
RECORD [
interface: LoganBerryRpcControl.InterfaceRecord,
conv: RPC.Conversation,
db: LoganBerry.OpenDB,
instance: RPC.ShortROPE,
dbName: ROPE
];
CursorInterface: TYPE = REF CursorInterfaceRecord;
CursorInterfaceRecord:
TYPE =
RECORD [
dbi: DBInterface,
cursor: LoganBerry.Cursor
];
LoganBerry operations
Error: PUBLIC ERROR [ec: ErrorCode, explanation: ROPE ← NIL] = CODE;
Open:
PUBLIC
PROC [conv: Conv ←
NIL, dbName:
ROPE]
RETURNS [db: OpenDB] ~ {
ENABLE {
RPC.CallFailed => {PostRpcCallError[why, NIL]; RETRY;};
LoganBerry.Error => ERROR Error[ec, explanation];
};
instance: RPC.ShortROPE;
interface: LoganBerryRpcControl.InterfaceRecord;
conversation: RPC.Conversation;
cp: FS.ComponentPositions;
fullDBName: ROPE;
remoteDB: LoganBerry.OpenDB;
[fullDBName, cp] ← FS.ExpandName[dbName];
instance ← Rope.Substr[fullDBName, cp.server.start, cp.server.length];
fullDBName ← Rope.Replace[base: fullDBName, start: cp.server.start, len: cp.server.length, with: NIL];
[interface, conversation] ← ImportLoganBerry[instance];
IF conv # NIL THEN conversation ← conv; -- use the conversation presented if possible
remoteDB ← interface.Open[conversation, fullDBName];
db ← SaveDBInterface[interface, conversation, remoteDB, instance, fullDBName];
};
ReadEntry:
PUBLIC
PROC [conv: Conv ←
NIL, db: OpenDB, key: AttributeType, value: AttributeValue]
RETURNS [entry: Entry, others:
BOOLEAN] ~ {
ENABLE {
RPC.CallFailed => {PostRpcCallError[why, GetDBInterface[db]]; RETRY;};
LoganBerry.Error => {PostLoganBerryError[ec, explanation, GetDBInterface[db]]; RETRY;};
};
dbi: DBInterface ← GetDBInterface[db];
IF conv # NIL THEN dbi.conv ← conv;
[entry, others] ← dbi.interface.ReadEntry[dbi.conv, dbi.db, key, value];
};
EnumerateEntries:
PUBLIC PROC [db: OpenDB, key: AttributeType, start: AttributeValue ←
NIL, end: AttributeValue ←
NIL, proc: EntryProc]
RETURNS [] ~ {
This operation can only be called on local databases.
ENABLE {
RPC.CallFailed => {PostRpcCallError[why, GetDBInterface[db]]; RETRY;};
LoganBerry.Error => {PostLoganBerryError[ec, explanation, GetDBInterface[db]]; RETRY;};
};
isLocal: BOOLEAN ← FALSE;
dbi: DBInterface ← GetDBInterface[db];
FOR l:
LIST
OF InstanceInterface ← interfaceCache, l.rest
WHILE l #
NIL
DO
IF Rope.Equal[l.first.instance, ""]
THEN {
IF l.first.interface = dbi.interface
THEN
-- beware: pointer comparison
isLocal ← TRUE;
EXIT;
};
ENDLOOP;
IF NOT isLocal THEN ERROR Error[$BadDBHandle, "Enumerates can only be done on local databases."];
dbi.interface.EnumerateEntries[dbi.db, key, start, end, proc];
};
GenerateEntries:
PUBLIC
PROC [conv: Conv ←
NIL, db: OpenDB, key: AttributeType, start: AttributeValue ←
NIL, end: AttributeValue ←
NIL]
RETURNS [cursor: Cursor] ~ {
ENABLE {
RPC.CallFailed => {PostRpcCallError[why, GetDBInterface[db]]; RETRY;};
LoganBerry.Error => {PostLoganBerryError[ec, explanation, GetDBInterface[db]]; RETRY;};
};
remoteCursor: LoganBerry.Cursor;
dbi: DBInterface ← GetDBInterface[db];
IF conv # NIL THEN dbi.conv ← conv;
remoteCursor ← dbi.interface.GenerateEntries[dbi.conv, dbi.db, key, start, end];
cursor ← SaveCursorInterface[dbi, remoteCursor];
};
NextEntry:
PUBLIC
PROC [conv: Conv ←
NIL, cursor: Cursor, dir: CursorDirection ← increasing]
RETURNS [entry: Entry] ~ {
ENABLE {
RPC.CallFailed => {PostRpcCallError[why, GetCursorInterface[cursor].dbi]; RETRY;};
LoganBerry.Error => {PostLoganBerryError[ec, explanation, GetCursorInterface[cursor].dbi]; RETRY;};
};
ci: CursorInterface ← GetCursorInterface[cursor];
IF conv # NIL THEN ci.dbi.conv ← conv;
entry ← ci.dbi.interface.NextEntry[ci.dbi.conv, ci.cursor, dir];
};
EndGenerate:
PUBLIC
PROC [conv: Conv ←
NIL, cursor: Cursor]
RETURNS [] ~ {
ENABLE {
RPC.CallFailed => {PostRpcCallError[why, GetCursorInterface[cursor].dbi]; RETRY;};
LoganBerry.Error => {PostLoganBerryError[ec, explanation, GetCursorInterface[cursor].dbi]; RETRY;};
};
ci: CursorInterface ← GetCursorInterface[cursor];
IF conv # NIL THEN ci.dbi.conv ← conv;
ci.dbi.interface.EndGenerate[ci.dbi.conv, ci.cursor];
};
WriteEntry:
PUBLIC
PROC [conv: Conv ←
NIL, db: OpenDB, entry: Entry, log: LogID ← activityLog, replace:
BOOLEAN ←
FALSE]
RETURNS [] ~ {
ENABLE {
RPC.CallFailed => {PostRpcCallError[why, GetDBInterface[db]]; RETRY;};
LoganBerry.Error => {PostLoganBerryError[ec, explanation, GetDBInterface[db]]; RETRY;};
};
dbi: DBInterface ← GetDBInterface[db];
IF conv # NIL THEN dbi.conv ← conv;
dbi.interface.WriteEntry[dbi.conv, dbi.db, entry, log, replace];
};
DeleteEntry:
PUBLIC
PROC [conv: Conv ←
NIL, db: OpenDB, key: AttributeType, value: AttributeValue]
RETURNS [] ~ {
ENABLE {
RPC.CallFailed => {PostRpcCallError[why, GetDBInterface[db]]; RETRY;};
LoganBerry.Error => {PostLoganBerryError[ec, explanation, GetDBInterface[db]]; RETRY;};
};
dbi: DBInterface ← GetDBInterface[db];
IF conv # NIL THEN dbi.conv ← conv;
dbi.interface.DeleteEntry[dbi.conv, dbi.db, key, value];
};
Close:
PUBLIC
PROC [conv: Conv ←
NIL, db: OpenDB]
RETURNS [] ~ {
ENABLE {
RPC.CallFailed => {PostRpcCallError[why, GetDBInterface[db]]; RETRY;};
LoganBerry.Error => {PostLoganBerryError[ec, explanation, GetDBInterface[db]]; RETRY;};
};
dbi: DBInterface ← GetDBInterface[db];
IF conv # NIL THEN dbi.conv ← conv;
dbi.interface.Close[dbi.conv, dbi.db];
};
BuildIndices:
PUBLIC
PROC [conv: Conv ←
NIL, db: OpenDB]
RETURNS [] ~ {
ENABLE {
RPC.CallFailed => {PostRpcCallError[why, GetDBInterface[db]]; RETRY;};
LoganBerry.Error => {PostLoganBerryError[ec, explanation, GetDBInterface[db]]; RETRY;};
};
dbi: DBInterface ← GetDBInterface[db];
IF conv # NIL THEN dbi.conv ← conv;
dbi.interface.BuildIndices[dbi.conv, dbi.db];
};
CompactLogs:
PUBLIC
PROC [conv: Conv ←
NIL, db: OpenDB]
RETURNS [] ~ {
ENABLE {
RPC.CallFailed => {PostRpcCallError[why, GetDBInterface[db]]; RETRY;};
LoganBerry.Error => {PostLoganBerryError[ec, explanation, GetDBInterface[db]]; RETRY;};
};
dbi: DBInterface ← GetDBInterface[db];
IF conv # NIL THEN dbi.conv ← conv;
dbi.interface.CompactLogs[dbi.conv, dbi.db];
};
Describe:
PUBLIC
PROC [conv: Conv ←
NIL, db: OpenDB]
RETURNS [info: SchemaInfo] ~ {
ENABLE {
RPC.CallFailed => {PostRpcCallError[why, GetDBInterface[db]]; RETRY;};
LoganBerry.Error => {PostLoganBerryError[ec, explanation, GetDBInterface[db]]; RETRY;};
};
dbi: DBInterface ← GetDBInterface[db];
IF conv # NIL THEN dbi.conv ← conv;
info ← dbi.interface.Describe[dbi.conv, dbi.db];
};
Exported to LoganBerryStubExtras for now...
Describe2:
PUBLIC
PROC [conv: Conv ←
NIL, db: OpenDB]
RETURNS [info: LoganBerryStubExtras.SchemaInfo] ~ {
ENABLE {
LoganBerry.Error => {PostLoganBerryError[ec, explanation, GetDBInterface[db]]; RETRY;};
};
dbi: DBInterface ← GetDBInterface[db];
IF conv # NIL THEN dbi.conv ← conv;
info ← LoganBerryExtras.Describe2[dbi.conv, dbi.db];
};
Importing interfaces
Keep a cache of interface records and conversations for various instances.
ImportLoganBerry:
PROC [instance:
RPC.ShortROPE]
RETURNS [interface: LoganBerryRpcControl.InterfaceRecord, conv: RPC.Conversation] ~ {
Searches the interface cache for an existing interface for the given instance. If one is not found then a new interface is imported. Also, a single conversation is used in conjunction with a given interface.
interface ← NIL;
FOR l:
LIST
OF InstanceInterface ← interfaceCache, l.rest
WHILE l #
NIL
DO
IF Rope.Equal[l.first.instance, instance]
THEN {
interface ← l.first.interface;
conv ← l.first.conv;
EXIT;
};
ENDLOOP;
IF interface =
NIL
THEN {
IF Rope.Equal[instance, ""]
THEN {
-- need local interface
interface ← NewLocalInterface[];
conv ← NIL;
}
ELSE {
-- import remote interface
interface ← LoganBerryRpcControl.ImportNewInterface[[type: loganBerryType, instance: instance, version: loganBerryVersion] ! RPC.ImportFailed => PostRpcImportError[why]];
conv ← NewConversation[instance];
};
interfaceCache ← CONS[NEW[InstanceInterfaceRecord ← [instance, interface, conv]], interfaceCache];
};
};
InvalidateInterface:
PROC [instance:
RPC.ShortROPE]
RETURNS [] ~ {
Removes the interface associated with the given instance from the interface cache. Subsequent attempts to import the instance will create a new cache entry.
prev: LIST OF InstanceInterface ← NIL;
FOR l:
LIST
OF InstanceInterface ← interfaceCache, l.rest
WHILE l #
NIL
DO
IF Rope.Equal[l.first.instance, instance]
THEN
IF prev =
NIL
THEN
interfaceCache ← l.rest
prev ← l;
ENDLOOP;
};
NewLocalInterface:
PROC []
RETURNS [interface: LoganBerryRpcControl.InterfaceRecord] ~ {
Creates an interface record and fills it in with procedures that do local, rather than remote, calls.
interface ← NEW[LoganBerryRpcControl.InterfaceRecordObject];
interface^ ← [Error: LoganBerry.Error, clientStubOpen: localOpen, clientStubDescribe: localDescribe, clientStubReadEntry: localReadEntry, clientStubEnumerateEntries: localEnumerateEntries, clientStubGenerateEntries: localGenerateEntries, clientStubNextEntry: localNextEntry, clientStubEndGenerate: localEndGenerate, clientStubWriteEntry: localWriteEntry, clientStubDeleteEntry: localDeleteEntry, clientStubClose: localClose, clientStubBuildIndices: localBuildIndices, clientStubCompactLogs: localCompactLogs];
};
local calls to the LoganBerry interface
localOpen:
PROC [interface: LoganBerryRpcControl.InterfaceRecord, conv: Conv ←
NIL, dbName:
ROPE]
RETURNS [db: OpenDB] ~ {
RETURN[LoganBerry.Open[conv, dbName]];
};
localReadEntry:
PROC [interface: LoganBerryRpcControl.InterfaceRecord, conv: Conv ←
NIL, db: OpenDB, key: AttributeType, value: AttributeValue]
RETURNS [entry: Entry, others:
BOOLEAN] ~ {
[entry, others] ← LoganBerry.ReadEntry[conv, db, key, value];
};
localEnumerateEntries:
PROC [interface: LoganBerryRpcControl.InterfaceRecord, db: OpenDB, key: AttributeType, start: AttributeValue ←
NIL, end: AttributeValue ←
NIL, proc: EntryProc]
RETURNS [] ~ {
LoganBerry.EnumerateEntries[db, key, start, end, proc];
};
localGenerateEntries:
PROC [interface: LoganBerryRpcControl.InterfaceRecord, conv: Conv ←
NIL, db: OpenDB, key: AttributeType, start: AttributeValue ←
NIL, end: AttributeValue ←
NIL]
RETURNS [cursor: Cursor] ~ {
RETURN[LoganBerry.GenerateEntries[conv, db, key, start, end]];
};
localNextEntry:
PROC [interface: LoganBerryRpcControl.InterfaceRecord, conv: Conv ←
NIL, cursor: Cursor, dir: CursorDirection ← increasing]
RETURNS [entry: Entry] ~ {
RETURN[LoganBerry.NextEntry[conv, cursor, dir]];
};
localEndGenerate:
PROC [interface: LoganBerryRpcControl.InterfaceRecord, conv: Conv ←
NIL, cursor: Cursor]
RETURNS [] ~ {
LoganBerry.EndGenerate[conv, cursor];
};
localWriteEntry:
PROC [interface: LoganBerryRpcControl.InterfaceRecord, conv: Conv ←
NIL, db: OpenDB, entry: Entry, log: LogID ← activityLog, replace:
BOOLEAN ←
FALSE]
RETURNS [] ~ {
LoganBerry.WriteEntry[conv, db, entry, log, replace];
};
localDeleteEntry:
PROC [interface: LoganBerryRpcControl.InterfaceRecord, conv: Conv ←
NIL, db: OpenDB, key: AttributeType, value: AttributeValue]
RETURNS [] ~ {
LoganBerry.DeleteEntry[conv, db, key, value];
};
localClose:
PROC [interface: LoganBerryRpcControl.InterfaceRecord, conv: Conv ←
NIL, db: OpenDB]
RETURNS [] ~ {
LoganBerry.Close[conv, db];
};
localBuildIndices:
PROC [interface: LoganBerryRpcControl.InterfaceRecord, conv: Conv ←
NIL, db: OpenDB]
RETURNS [] ~ {
LoganBerry.BuildIndices[conv, db];
};
localCompactLogs:
PROC [interface: LoganBerryRpcControl.InterfaceRecord, conv: Conv ←
NIL, db: OpenDB]
RETURNS [] ~ {
LoganBerry.CompactLogs[conv, db];
};
localDescribe:
PROC [interface: LoganBerryRpcControl.InterfaceRecord, conv: Conv ←
NIL, db: OpenDB]
RETURNS [info: SchemaInfo] ~ {
RETURN[LoganBerry.Describe[conv, db]];
};
Handles and interface mappings
Keep a table mapping open database handles to the interfaces that should be used for them. The database handle presented to clients of LoganBerryStub is not the same as the one returned by a remote LoganBerry server since remote handles are not globally unique. Thus, a new (unique) handle is generated. This handle is mapped to the actual remote handle, the proper interface, and the conversation on every call. Cursors are handled in a similar manner.
Note: there are probably more efficient ways to perform these mappings than using the RefID table.
SaveDBInterface:
PROC [interface: LoganBerryRpcControl.InterfaceRecord, conv:
RPC.Conversation, db: LoganBerry.OpenDB, instance: RPC.ShortROPE, dbName:
ROPE]
RETURNS [localDB: LoganBerry.OpenDB] ~ {
dbi: DBInterface ← NEW[DBInterfaceRecord ← [interface, conv, db, instance, dbName]];
RETURN[RefID.Seal[dbi]];
};
GetDBInterface:
PROC [localDB: LoganBerry.OpenDB]
RETURNS [dbi: DBInterface] ~ {
ref: REF = RefID.Unseal[localDB];
IF ref =
NIL
THEN
ERROR Error[$BadDBHandle, "NIL OpenDB handle."];
WITH ref
SELECT
FROM
dbi: DBInterface => RETURN[dbi];
ENDCASE => ERROR Error[$BadDBHandle, "Invalid OpenDB handle."];
};
SaveCursorInterface:
PROC [dbi: DBInterface, cursor: LoganBerry.Cursor]
RETURNS [localCursor: LoganBerry.Cursor] ~ {
ci: CursorInterface ← NEW[CursorInterfaceRecord ← [dbi, cursor]];
RETURN[RefID.Seal[ci]];
};
GetCursorInterface:
PROC [localCursor: LoganBerry.Cursor]
RETURNS [ci: CursorInterface] ~ {
ref: REF = RefID.Unseal[localCursor];
IF ref =
NIL
THEN
ERROR Error[$BadCursor, "NIL cursor handle."];
WITH ref
SELECT
FROM
ci: CursorInterface => RETURN[ci];
ENDCASE => ERROR Error[$BadCursor, "Invalid cursor passed to NextEntry."];
};
Doug Terry, April 18, 1986 3:40:47 pm PST
Support for multiple import and efficient local import.
changes to: ~, ImportLoganBerry, NewLocalInterface, localOpen, localReadEntry, localEnumerateEntries, localGenerateEntries, localNextEntry, localEndGenerate, localWriteEntry, localDeleteEntry, localClose, localBuildIndices, localCompactLogs, localDescribe, Open, ReadEntry, NewConversation, EnumerateEntries, GenerateEntries, NextEntry, EndGenerate, WriteEntry, DeleteEntry, Close, BuildIndices, CompactLogs, Describe, SaveDBInterface, GetDBInterface, SaveCursorInterface, GetCursorInterface, DIRECTORY, IMPORTS
Doug Terry, April 23, 1986 3:58:07 pm PST
Added error handling routines.
changes to: ~, PostLBError, Open, SaveDBInterface, ReadEntry, DIRECTORY, NewConversation, PostRPCError, PostRpcAuthError, ImportLoganBerry, PostRpcImportError, InvalidateInterface, GenerateEntries, NextEntry, EndGenerate, SaveCursorInterface, EnumerateEntries, WriteEntry, DeleteEntry, Close, BuildIndices, CompactLogs, Describe