DIRECTORY Atom USING [GetPName, MakeAtom], Camelot USING [tidT], Process USING [-- Detach, -- MsecToTicks, SetTimeout], RedBlackTree USING [Compare, Create, Delete, GetKey, Insert, Lookup, Table], Rope USING [Concat, Equal, Match, ROPE, Substr], YggDID USING [DID, EqualDIDs], YggDIDMap USING [AddComponentFile, GetComponentFiles, OpenDocumentFromDID, RemoveComponentFile], YggDIDMapPrivate USING [Document, DocumentRep], YggDIDPrivate USING [DIDRep], YggEnvironment USING [nullTransID, TransID], YggFile USING [Create, Delete, FileFromComponentFiles, FileUseFromComponentFiles], YggFixedNames USING [ChildContainers, Children, IndexPrefix, IndexPrefixSize, ParentContainers, Parents], YggIndex USING [DeleteEntry, EntryProc, EnumerateEntries, Index, Open, WriteEntry], YggIndexMaint USING [], YggInternal USING [FileHandle], YggNav USING [GetAllProperties, GetContainersInContainer, GetObjectsInContainer, GetPatternsInContainer, GetSuperContainers], YggRep USING [Attribute, AttributeValue, CompareTimeStamps, did, MaxTimeStamp, metaAttributeMod, NextTimeStamp, nullTimeStamp, TimeStamp, TypedPrimitiveElement, VDoc, VolatizeFromDID], YggTransaction USING [CreateTrans, EqualTrans, Finish, Outcome]; YggIndexMaintImpl: CEDAR MONITOR IMPORTS Atom, Process, RedBlackTree, Rope, YggDID, YggDIDMap, YggFile, YggFixedNames, YggIndex, YggNav, YggRep, YggTransaction EXPORTS YggDID, YggInternal, YggIndexMaint ~ BEGIN ROPE: TYPE = Rope.ROPE; Document: TYPE = REF DocumentRep; DocumentRep: PUBLIC TYPE = YggDIDMapPrivate.DocumentRep; DID: PUBLIC TYPE ~ REF DIDRep; DIDRep: PUBLIC TYPE ~ YggDIDPrivate.DIDRep; DIDTidNotFound: PUBLIC ERROR = CODE; DoingIndexMaint: BOOL _ FALSE; MyCondition: CONDITION; UpdateType: TYPE = {valueChange, addIndex, deleteIndex, metaChanged}; currentTimeStamp: YggRep.TimeStamp _ YggRep.nullTimeStamp; WorkItem: TYPE = RECORD[ did: DID, tid: Camelot.tidT, transactionCommitted: {unknown, abort, commit}, updateType: UpdateType, attributeOrPattern: ROPE, oldValues: LIST OF LIST OF YggRep.AttributeValue, newValues: LIST OF LIST OF YggRep.AttributeValue, metaAttributesChanged: LIST OF YggRep.metaAttributeMod, containersToApply: LIST OF ContainerItem, maxTimeStamp: YggRep.TimeStamp _ YggRep.nullTimeStamp, containersToIndex: LIST OF ContainerItem, attributePatternDIDs: LIST OF AttrPatDID _ NIL ]; ContainerItem: TYPE = RECORD[ did: DID, done: BOOL _ FALSE, attributeNamePattern: LIST OF ROPE _ NIL ]; WorkItemsForDID: TYPE = RECORD[ stuffToDo: LIST OF WorkItem ]; WorkToDoList: LIST OF WorkItemsForDID _ NIL; AttrPatDID: TYPE = RECORD[ attributeNamePattern: ROPE, attributes: LIST OF AttrDIDVal ]; AttrDIDVal: TYPE = RECORD[ attributeName: ROPE, didsAndValues: LIST OF DIDVal _ NIL ]; DIDVal: TYPE = RECORD[ did: DID, values: LIST OF LIST OF YggRep.AttributeValue _ NIL ]; containerLock: RedBlackTree.Table; numLockedContainers: INT _ 0; containerLockEntry: TYPE ~ REF containerLockRep; containerLockRep: TYPE ~ RECORD [ did: DID, users: INT _ 0 ]; NewValueForAttribute: PUBLIC PROC [did: YggDID.DID, tid: Camelot.tidT, attributeName: ROPE, oldValues: LIST OF LIST OF YggRep.AttributeValue, newValues: LIST OF LIST OF YggRep.AttributeValue] ~ { addValueToWorkToDoList[did, tid, attributeName, oldValues, newValues]; }; NewValueForMetaAttribute: PUBLIC PROC [did: YggDID.DID, tid: Camelot.tidT, metaAttributesChanged: LIST OF YggRep.metaAttributeMod] ~ { addNewValueForMetaAttributeToWorkToDoList[did, tid, metaAttributesChanged]; }; NewValueForAttributeCommitStatus: PUBLIC PROC [did: YggDID.DID, tid: Camelot.tidT, commited: BOOL] ~ { noteCommit[did, tid, commited]; }; AddOrRemoveIndexPattern: PUBLIC PROC [containerDID: YggDID.DID, pattern: ROPE, add: BOOL] RETURNS [ok: BOOL _ TRUE]~ { addIndexUpdateToWorkToDoList[containerDID, pattern, add]; }; addValueToWorkToDoList: ENTRY PROC[did: YggDID.DID, tid: Camelot.tidT, attributeName: ROPE, oldValues: LIST OF LIST OF YggRep.AttributeValue, newValues: LIST OF LIST OF YggRep.AttributeValue] ~ { lastwtdl: LIST OF WorkItemsForDID _ NIL; IF ~DoingIndexMaint THEN RETURN; FOR wtdl: LIST OF WorkItemsForDID _ WorkToDoList, wtdl.rest UNTIL wtdl = NIL DO IF YggDID.EqualDIDs[wtdl.first.stuffToDo.first.did, did] THEN { lastWI: LIST OF WorkItem _ NIL; FOR lowi: LIST OF WorkItem _ wtdl.first.stuffToDo, lowi.rest UNTIL lowi = NIL DO lastWI _ lowi; ENDLOOP; lastWI.rest _ CONS[[did, tid, unknown, valueChange, attributeName, oldValues, newValues, NIL, NIL], NIL]; EXIT; }; lastwtdl _ wtdl; REPEAT FINISHED => { vta: LIST OF WorkItemsForDID _ LIST[[LIST[[did, tid, unknown, valueChange, attributeName, oldValues, newValues, NIL]]]]; IF lastwtdl = NIL THEN WorkToDoList _ vta ELSE lastwtdl.rest _ vta; }; ENDLOOP; NOTIFY MyCondition; }; addIndexUpdateToWorkToDoList: ENTRY PROC[containerDID: YggDID.DID, pattern: ROPE, add: BOOL] ~ { lastwtdl: LIST OF WorkItemsForDID _ NIL; IF ~DoingIndexMaint THEN RETURN; FOR wtdl: LIST OF WorkItemsForDID _ WorkToDoList, wtdl.rest UNTIL wtdl = NIL DO IF YggDID.EqualDIDs[wtdl.first.stuffToDo.first.did, containerDID] THEN { lastWI: LIST OF WorkItem _ NIL; FOR lowi: LIST OF WorkItem _ wtdl.first.stuffToDo, lowi.rest UNTIL lowi = NIL DO lastWI _ lowi; ENDLOOP; lastWI.rest _ CONS[[containerDID, YggEnvironment.nullTransID, commit, IF add THEN addIndex ELSE deleteIndex, pattern, NIL, NIL, NIL, NIL], NIL]; EXIT; }; lastwtdl _ wtdl; REPEAT FINISHED => { vta: LIST OF WorkItemsForDID _ LIST[[LIST[[containerDID, YggEnvironment.nullTransID, commit, IF add THEN addIndex ELSE deleteIndex, pattern, NIL, NIL, NIL, NIL]]]]; IF lastwtdl = NIL THEN WorkToDoList _ vta ELSE lastwtdl.rest _ vta; }; ENDLOOP; bumpContainerUse[containerDID]; NOTIFY MyCondition; }; addNewValueForMetaAttributeToWorkToDoList: ENTRY PROC[did: YggDID.DID, tid: Camelot.tidT, metaAttributesChanged: LIST OF YggRep.metaAttributeMod] ~ { lastwtdl: LIST OF WorkItemsForDID _ NIL; IF ~DoingIndexMaint THEN RETURN; FOR wtdl: LIST OF WorkItemsForDID _ WorkToDoList, wtdl.rest UNTIL wtdl = NIL DO IF YggDID.EqualDIDs[wtdl.first.stuffToDo.first.did, did] THEN { lastWI: LIST OF WorkItem _ NIL; FOR lowi: LIST OF WorkItem _ wtdl.first.stuffToDo, lowi.rest UNTIL lowi = NIL DO lastWI _ lowi; ENDLOOP; lastWI.rest _ CONS[[did, tid, unknown, metaChanged, NIL, NIL, NIL, metaAttributesChanged, NIL], NIL]; EXIT; }; lastwtdl _ wtdl; REPEAT FINISHED => { vta: LIST OF WorkItemsForDID _ LIST[[LIST[[did, tid, unknown, metaChanged, NIL, NIL, NIL, metaAttributesChanged, NIL]]]]; IF lastwtdl = NIL THEN WorkToDoList _ vta ELSE lastwtdl.rest _ vta; }; ENDLOOP; bumpContainerUse[did]; NOTIFY MyCondition; }; noteCommit: ENTRY PROC [did: YggDID.DID, tid: Camelot.tidT, commited: BOOL] ~ { ENABLE UNWIND => {}; gotOne: BOOL _ FALSE; IF ~DoingIndexMaint THEN RETURN; FOR wtdl: LIST OF WorkItemsForDID _ WorkToDoList, wtdl.rest UNTIL wtdl = NIL DO IF YggDID.EqualDIDs[wtdl.first.first.did, did] THEN { FOR lowi: LIST OF WorkItem _ wtdl.first.stuffToDo, lowi.rest UNTIL lowi = NIL DO IF YggTransaction.EqualTrans[lowi.first.tid, tid] THEN { lowi.first.transactionCommitted _ IF commited THEN commit ELSE abort; gotOne _ TRUE; }; ENDLOOP; EXIT; }; REPEAT FINISHED => { ERROR DIDTidNotFound; }; ENDLOOP; IF ~gotOne THEN ERROR DIDTidNotFound; IF ~commited THEN bumpContainerUse[did]; NOTIFY MyCondition; }; IndexMaintProcess: PROC = { DO -- forever waitForABit: ENTRY PROC ~ { WAIT MyCondition; }; didSomething: BOOL _ FALSE; prevDID: LIST OF WorkItemsForDID _ NIL; FOR wtdl: LIST OF WorkItemsForDID _ WorkToDoList, wtdl.rest UNTIL wtdl = NIL DO doneWithDID: BOOL _ FALSE; entryRemoved: BOOL _ FALSE; prevWorkItems: LIST OF WorkItemsForDID _ NIL; removeEntry: ENTRY PROC ~ { IF prevWorkItems # NIL THEN prevWorkItems.first.rest _ wtdl.first.rest -- previous work item for DID still active ELSE { -- first work item for this DID IF prevDID = NIL THEN { -- first DID on the list IF wtdl.first.rest = NIL THEN { -- all updates for did are done and this is the first did on the list WorkToDoList _ wtdl.rest; } ELSE { -- more updates for did, this is the first did on the list, and this is the first update on the list wtdl.first.stuffToDo _ wtdl.first.stuffToDo.rest; }; } ELSE { IF wtdl.first.rest = NIL THEN { -- all updates for did are done and this is not the first did on the list prevDID.rest _ wtdl.rest; } ELSE { -- more updates for did and this is not the first did on the list wtdl.first.stuffToDo _ wtdl.first.stuffToDo.rest; }; }; }; }; vDoc: YggRep.VDoc _ NIL; vDoc _ YggRep.VolatizeFromDID[YggEnvironment.nullTransID, wtdl.first.first.did, readOnly, [read, fail], TRUE]; IF vDoc = NIL THEN {prevWorkItems _ wtdl; LOOP;}; -- but what if the did has been deleted? entryRemoved _ FALSE; FOR lowi: LIST OF WorkItem _ wtdl.first, lowi.rest UNTIL lowi = NIL DO doRemoveEntry: BOOL _ FALSE; somethingChanged: BOOL _ FALSE; IF lowi.first.transactionCommitted = unknown THEN LOOP; -- waiting for the commit state to be known IF lowi.first.transactionCommitted = abort THEN {removeEntry[]; LOOP}; -- transaction didn't make it; dump item SELECT lowi.first.updateType FROM valueChange => { [doRemoveEntry, somethingChanged] _ doValueChange[lowi, vDoc]; }; addIndex => { [doRemoveEntry, somethingChanged] _ doCreateIndexChange[lowi, vDoc]; }; deleteIndex => { [doRemoveEntry, somethingChanged] _ doDeleteIndexChange[lowi, vDoc]; }; metaChanged => { [doRemoveEntry, somethingChanged] _ doMetaChange[lowi, vDoc]; }; ENDCASE => ERROR; IF doRemoveEntry THEN { entryRemoved _ TRUE; removeEntry[]; didSomething _ TRUE; }; IF ~entryRemoved THEN prevWorkItems _ wtdl; prevDID _ wtdl; ENDLOOP; -- for a DID ENDLOOP; -- Flip through the work to do list IF ~didSomething THEN waitForABit[]; ENDLOOP; -- forever }; doValueChange: PROC [lowi: LIST OF WorkItem, vDoc: YggRep.VDoc] RETURNS [doRemoveEntry: BOOL _ FALSE, somethingChanged: BOOL _ FALSE] ~ { keepTrying: BOOL _ TRUE; IF lowi.first.containersToApply = NIL THEN getContainersToApply[vDoc, lowi, YggFixedNames.Parents]; -- just starting, so find parents WHILE keepTrying DO FOR parentContainers: LIST OF ContainerItem _ lowi.first.containersToApply, parentContainers.rest UNTIL parentContainers = NIL DO doc: Document; atomForIndex: ATOM; transID: YggEnvironment.TransID; componentFiles: LIST OF YggInternal.FileHandle; outcome: YggTransaction.Outcome; superDIDs: LIST OF DID _ NIL; success: BOOL _ FALSE; indexFile: YggInternal.FileHandle; IF parentContainers.first.done THEN LOOP; -- already done transID _ YggTransaction.CreateTrans[YggEnvironment.nullTransID]; doc _ YggDIDMap.OpenDocumentFromDID[parentContainers.first.did, transID]; lowi.first.maxTimeStamp _ YggRep.MaxTimeStamp[lowi.first.maxTimeStamp, doc.timeStampForIndexMaint]; componentFiles _ YggDIDMap.GetComponentFiles[doc]; atomForIndex _ Atom.MakeAtom[Rope.Concat[YggFixedNames.IndexPrefix, lowi.first.attributeOrPattern]]; indexFile _ YggFile.FileFromComponentFiles[componentFiles: componentFiles, fileUse: atomForIndex]; [superDIDs, success] _ YggNav.GetSuperContainers[transID, parentContainers.first.did, TRUE]; IF ~success THEN { outcome _ YggTransaction.Finish[transID: transID, requestedOutcome: abort]; keepTrying _ FALSE; EXIT; }; [keepTrying: keepTrying, outcome: outcome] _ doOneChange[transID: transID, doc: doc, indexFile: indexFile, attributeName: lowi.first.attributeOrPattern, atomForIndex: atomForIndex, containerDID: parentContainers.first.did, indexedDID: lowi.first.did, oldValues: lowi.first.oldValues, newValues: lowi.first.newValues]; IF ~keepTrying THEN EXIT; outcome _ YggTransaction.Finish[transID: transID, requestedOutcome: commit]; IF outcome = commit THEN { somethingChanged _ TRUE; parentContainers.first.done _ TRUE; IF superDIDs # NIL THEN addSuperDIDs[superDIDs, lowi]; -- append supers to list of stuff to do, provided that they have not yet been seen } ELSE { keepTrying _ FALSE; EXIT; }; IF ~keepTrying THEN EXIT; REPEAT FINISHED => { -- all parents processed OK !!! ok: BOOL _ TRUE; ok _ checkTimeStamps[lowi, lowi.first.maxTimeStamp]; IF ok THEN { doRemoveEntry _ TRUE; somethingChanged _ TRUE; keepTrying _ FALSE; } ELSE { -- something changed; force re-evaluation of my super contatiners and redo loop FOR parentContainers: LIST OF ContainerItem _ lowi.first.containersToApply, parentContainers.rest UNTIL parentContainers = NIL DO parentContainers.first.done _ FALSE; ENDLOOP; }; }; ENDLOOP; ENDLOOP; }; doMetaChange: PROC [lowi: LIST OF WorkItem, vDoc: YggRep.VDoc] RETURNS [doRemoveEntry: BOOL _ FALSE, somethingChanged: BOOL _ FALSE] ~ { FOR mam: LIST OF YggRep.metaAttributeMod _ lowi.first.metaAttributesChanged, mam.rest UNTIL mam = NIL DO SELECT TRUE FROM Rope.Equal[YggFixedNames.Parents, mam.first.attributeName] => { doRemoveEntry _ TRUE; somethingChanged _ TRUE; }; Rope.Equal[YggFixedNames.Children, mam.first.attributeName] => { -- add/remove DID as child to this parent (in WorkItem) keepTrying: BOOL _ TRUE; didSomething: BOOL _ TRUE; outcome: YggTransaction.Outcome; containerDID: DID; transID: YggEnvironment.TransID; containerDoc: Document; containerVDoc: YggRep.VDoc; containerDID _ mam.first.didValue; containerVDoc _ YggRep.VolatizeFromDID[YggEnvironment.nullTransID, containerDID, readOnly, [read, fail], TRUE]; IF containerVDoc = NIL THEN RETURN [FALSE, FALSE]; -- try again later IF lowi.first.containersToApply = NIL THEN lowi.first.containersToApply _ LIST[[containerDID, FALSE]]; WHILE keepTrying DO FOR parentContainers: LIST OF ContainerItem _ lowi.first.containersToApply, parentContainers.rest UNTIL parentContainers = NIL DO superDIDs: LIST OF DID _ NIL; success: BOOL _ FALSE; IF parentContainers.first.done THEN LOOP; -- already done transID _ YggTransaction.CreateTrans[YggEnvironment.nullTransID]; containerDoc _ YggDIDMap.OpenDocumentFromDID[parentContainers.first.did, transID]; lowi.first.maxTimeStamp _ YggRep.MaxTimeStamp[lowi.first.maxTimeStamp, containerDoc.timeStampForIndexMaint]; [superDIDs, success] _ YggNav.GetSuperContainers[transID, parentContainers.first.did, TRUE]; IF ~success THEN { outcome _ YggTransaction.Finish[transID: transID, requestedOutcome: abort]; keepTrying _ FALSE; EXIT; }; FOR aL: LIST OF YggRep.Attribute _ vDoc.attributes, aL.rest UNTIL aL = NIL DO attributeName: ROPE _ aL.first.attributeName; [keepTrying: keepTrying, outcome: outcome, didSomething: didSomething] _ doOneChange [ transID: transID, doc: containerDoc, indexFile: NIL, attributeName: attributeName, atomForIndex: Atom.MakeAtom[Rope.Concat[ YggFixedNames.IndexPrefix, attributeName]], containerDID: containerDID, indexedDID: vDoc.did, oldValues: IF mam.first.add THEN NIL ELSE LIST[aL.first.value], newValues: IF mam.first.add THEN LIST[aL.first.value] ELSE NIL]; IF ~keepTrying THEN EXIT; IF didSomething THEN { outcome _ YggTransaction.Finish[transID: transID, requestedOutcome: commit]; IF outcome = commit THEN { somethingChanged _ TRUE; IF aL.rest # NIL THEN transID _ YggTransaction.CreateTrans[YggEnvironment.nullTransID]; } ELSE { keepTrying _ FALSE; EXIT; }; }; REPEAT FINISHED => { -- container processed OK !!! parentContainers.first.done _ TRUE; IF superDIDs # NIL THEN addSuperDIDs[superDIDs, lowi]; -- append supers to list of stuff to do, provided that they have not yet been seen }; ENDLOOP; outcome _ YggTransaction.Finish[transID: transID, requestedOutcome: abort]; IF ~keepTrying THEN EXIT; REPEAT FINISHED => { -- all parents processed OK !!! ok: BOOL _ TRUE; ok _ checkTimeStamps[lowi, lowi.first.maxTimeStamp]; IF ok THEN { doRemoveEntry _ TRUE; somethingChanged _ TRUE; keepTrying _ FALSE; } ELSE { -- something changed; force re-evaluation of my super contatiners and redo loop FOR parentContainers: LIST OF ContainerItem _ lowi.first.containersToApply, parentContainers.rest UNTIL parentContainers = NIL DO parentContainers.first.done _ FALSE; ENDLOOP; }; }; ENDLOOP; ENDLOOP; }; Rope.Equal[YggFixedNames.ParentContainers, mam.first.attributeName] => { doRemoveEntry _ TRUE; somethingChanged _ TRUE; }; Rope.Equal[YggFixedNames.ChildContainers, mam.first.attributeName] => { keepTrying: BOOL _ TRUE; didSomething: BOOL _ TRUE; outcome: YggTransaction.Outcome; containerDID: DID; transID: YggEnvironment.TransID; containerDoc: Document; containerDID _ mam.first.didValue; WHILE keepTrying DO IF lowi.first.containersToApply = NIL THEN { lastContainersToApply: LIST OF ContainerItem; lastContainersToApply _ lowi.first.containersToApply _ LIST[[containerDID, FALSE]]; transID _ YggTransaction.CreateTrans[YggEnvironment.nullTransID]; FOR cta: LIST OF ContainerItem _ lowi.first.containersToApply, cta.rest UNTIL cta = NIL DO superDIDs: LIST OF DID _ NIL; success: BOOL _ FALSE; patterns: LIST OF ROPE; containerDoc _ YggDIDMap.OpenDocumentFromDID[cta.first.did, transID]; lowi.first.maxTimeStamp _ YggRep.MaxTimeStamp[lowi.first.maxTimeStamp, containerDoc.timeStampForIndexMaint]; [superDIDs, success] _ YggNav.GetSuperContainers[transID, cta.first.did, TRUE]; IF ~success THEN { outcome _ YggTransaction.Finish[transID: transID, requestedOutcome: abort]; keepTrying _ FALSE; EXIT; }; FOR dids: LIST OF DID _ superDIDs, dids.rest UNTIL dids = NIL DO FOR knownSupers: LIST OF ContainerItem _ lowi.first.containersToApply, knownSupers.rest UNTIL knownSupers = NIL DO IF YggDID.EqualDIDs[knownSupers.first.did, dids.first] THEN EXIT; REPEAT FINISHED => { lastContainersToApply _ CONS[[dids.first, FALSE], lastContainersToApply]; }; ENDLOOP; ENDLOOP; [patterns, success] _ YggNav.GetPatternsInContainer[cta.first.did, TRUE]; IF ~success THEN { keepTrying _ FALSE; EXIT; }; cta.first.attributeNamePattern _ patterns; FOR lop: LIST OF ROPE _ patterns, lop.rest UNTIL lop = NIL DO FOR loapd: LIST OF AttrPatDID _ lowi.first.attributePatternDIDs, loapd.rest UNTIL loapd = NIL DO IF Rope.Equal[loapd.first.attributeNamePattern, lop.first] THEN EXIT; REPEAT FINISHED => { lowi.first.attributePatternDIDs _ CONS[[loapd.first.attributeNamePattern, NIL], lowi.first.attributePatternDIDs]; }; ENDLOOP; ENDLOOP; ENDLOOP; IF ~ keepTrying THEN { lowi.first.containersToApply _ NIL; RETURN [FALSE, FALSE]; -- try again later }; outcome _ YggTransaction.Finish[transID: transID, requestedOutcome: commit]; }; IF ~keepTrying THEN EXIT; -- should be redundant IF lowi.first.containersToIndex = NIL THEN lowi.first.containersToIndex _ LIST[[lowi.first.did, FALSE]]; { FOR cta: LIST OF ContainerItem _ lowi.first.containersToIndex, cta.rest UNTIL cta = NIL OR ~keepTrying DO subDIDs: LIST OF DID _ NIL; didsInContainer: LIST OF DID _ NIL; success: BOOL _ FALSE; IF cta.first.done THEN LOOP; transID _ YggTransaction.CreateTrans[YggEnvironment.nullTransID]; containerDoc _ YggDIDMap.OpenDocumentFromDID[cta.first.did, transID]; [subDIDs, success] _ YggNav.GetContainersInContainer[transID, cta.first.did, TRUE]; IF ~success THEN { outcome _ YggTransaction.Finish[transID: transID, requestedOutcome: abort]; keepTrying _ FALSE; EXIT; }; FOR dids: LIST OF DID _ subDIDs, dids.rest UNTIL dids = NIL DO lastKnownSubs: LIST OF ContainerItem _ NIL; FOR knownSubs: LIST OF ContainerItem _ lowi.first.containersToIndex, knownSubs.rest UNTIL knownSubs = NIL DO lastKnownSubs _ knownSubs; IF YggDID.EqualDIDs[knownSubs.first.did, dids.first] THEN EXIT; REPEAT FINISHED => { lastKnownSubs _ CONS[[dids.first, FALSE], lastKnownSubs]; }; ENDLOOP; ENDLOOP; [dids: didsInContainer, success: success] _ YggNav.GetObjectsInContainer[trans: transID, containerDID: cta.first.did, dontWait: TRUE]; IF ~success THEN { outcome _ YggTransaction.Finish[transID: transID, requestedOutcome: abort]; keepTrying _ FALSE; RETURN; }; outcome _ YggTransaction.Finish[transID: transID, requestedOutcome: commit]; FOR nowDID: LIST OF DID _ didsInContainer, nowDID.rest UNTIL nowDID = NIL DO skipDID: BOOL _ FALSE; addedValueForDID: BOOL _ FALSE; properties: LIST OF YggRep.Attribute _ NIL; transID _ YggTransaction.CreateTrans[YggEnvironment.nullTransID]; [properties: properties] _ YggNav.GetAllProperties[trans: transID, did: nowDID.first]; outcome _ YggTransaction.Finish[transID: transID, requestedOutcome: commit]; FOR loa: LIST OF YggRep.Attribute _ properties, loa.rest UNTIL loa = NIL OR skipDID DO -- for each attribute FOR apd: LIST OF AttrPatDID _ lowi.first.attributePatternDIDs, apd.rest UNTIL apd = NIL OR skipDID DO IF Rope.Match[apd.first.attributeNamePattern, loa.first.attributeName] THEN { -- if the attribute should be indexed FOR apdItem: LIST OF AttrDIDVal _ apd.first.attributes, apdItem.rest UNTIL apdItem = NIL OR skipDID DO -- look for the attribute name under the pattern IF Rope.Equal[apdItem.first.attributeName, loa.first.attributeName] THEN { FOR didOnList: LIST OF DIDVal _ apdItem.first.didsAndValues, didOnList.rest UNTIL didOnList = NIL DO IF YggDID.EqualDIDs[didOnList.first.did, nowDID.first] THEN { IF addedValueForDID THEN { didOnList.first.values _ CONS[loa.first.value, didOnList.first.values]; } ELSE { skipDID _ TRUE; EXIT; }; }; REPEAT FINISHED => { addedValueForDID _ TRUE; apdItem.first.didsAndValues _ CONS[[nowDID.first, LIST[loa.first.value]], apdItem.first.didsAndValues]; }; ENDLOOP; EXIT; }; REPEAT FINISHED => IF ~skipDID THEN { -- attribute name not listed under pattern addedValueForDID _ TRUE; apd.first.attributes _ CONS[[loa.first.attributeName, LIST[[nowDID.first, LIST[loa.first.value]]]], apd.first.attributes]; }; ENDLOOP; }; ENDLOOP; ENDLOOP; ENDLOOP; cta.first.done _ TRUE; ENDLOOP; }; IF ~ keepTrying THEN EXIT; { FOR cta: LIST OF ContainerItem _ lowi.first.containersToApply, cta.rest UNTIL cta = NIL DO IF cta.first.done THEN LOOP; FOR anp: LIST OF ROPE _ cta.first.attributeNamePattern, anp.rest UNTIL anp = NIL DO FOR apd: LIST OF AttrPatDID _ lowi.first.attributePatternDIDs, apd.rest UNTIL apd = NIL DO IF Rope.Equal[apd.first.attributeNamePattern, anp.first] THEN { FOR apdItem: LIST OF AttrDIDVal _ apd.first.attributes, apdItem.rest UNTIL apdItem = NIL DO indexFile: YggInternal.FileHandle; containerDoc: Document; atomForIndex: ATOM; componentFiles: LIST OF YggInternal.FileHandle; transID _ YggTransaction.CreateTrans[YggEnvironment.nullTransID]; containerDoc _ YggDIDMap.OpenDocumentFromDID[cta.first.did, transID]; componentFiles _ YggDIDMap.GetComponentFiles[containerDoc]; atomForIndex _ Atom.MakeAtom[Rope.Concat[YggFixedNames.IndexPrefix, apdItem.first.attributeName]]; indexFile _ YggFile.FileFromComponentFiles[componentFiles: componentFiles, fileUse: atomForIndex]; FOR didsToIndex: LIST OF DIDVal _ apdItem.first.didsAndValues, didsToIndex.rest UNTIL didsToIndex = NIL DO [keepTrying: keepTrying, outcome: outcome, didSomething: didSomething] _ doOneChange [ transID: transID, doc: containerDoc, indexFile: indexFile, attributeName: apdItem.first.attributeName, atomForIndex: atomForIndex, containerDID: cta.first.did, indexedDID: didsToIndex.first.did, oldValues: IF mam.first.add THEN NIL ELSE didsToIndex.first.values, newValues: IF mam.first.add THEN didsToIndex.first.values ELSE NIL]; IF ~keepTrying THEN EXIT; IF didSomething AND indexFile = NIL THEN { indexFile _ YggFile.FileFromComponentFiles[componentFiles: componentFiles, fileUse: atomForIndex]; }; ENDLOOP; REPEAT FINISHED => { -- container processed OK !!! cta.first.done _ TRUE; }; ENDLOOP; EXIT; }; REPEAT FINISHED => ERROR; ENDLOOP; ENDLOOP; ENDLOOP; }; IF ~ keepTrying THEN EXIT; { ok: BOOL _ TRUE; ok _ checkTimeStamps[lowi, lowi.first.maxTimeStamp]; IF ok THEN FOR indexContainers: LIST OF ContainerItem _ lowi.first.containersToIndex, indexContainers.rest UNTIL indexContainers = NIL DO doc: Document; doc _ YggDIDMap.OpenDocumentFromDID[indexContainers.first.did, YggEnvironment.nullTransID]; IF YggRep.CompareTimeStamps[doc.timeStampForIndexMaint, lowi.first.maxTimeStamp] = greater THEN { ok _ FALSE; EXIT; }; ENDLOOP; IF ok THEN { doRemoveEntry _ TRUE; somethingChanged _ TRUE; keepTrying _ FALSE; } ELSE { -- something changed; force re-evaluation of my super contatiners and index containter and redo loop (shucks) lowi.first.containersToApply _ NIL; lowi.first.containersToIndex _ NIL; }; }; ENDLOOP; }; ENDCASE => ERROR; ENDLOOP; }; doCreateIndexChange: PROC [lowi: LIST OF WorkItem, vDoc: YggRep.VDoc] RETURNS [doRemoveEntry: BOOL _ FALSE, somethingChanged: BOOL _ FALSE] ~ { keepTrying: BOOL _ TRUE; didSomething: BOOL _ TRUE; outcome: YggTransaction.Outcome; containerDID: DID _ lowi.first.did; transID: YggEnvironment.TransID; containerDoc: Document; newPatternToIndex: ROPE _ lowi.first.attributeOrPattern; WHILE keepTrying DO success: BOOL _ FALSE; fileUses: LIST OF ATOM _ NIL; IF lowi.first.containersToIndex = NIL THEN lowi.first.containersToIndex _ LIST[[lowi.first.did, FALSE]]; IF lowi.first.attributePatternDIDs = NIL THEN lowi.first.attributePatternDIDs _ LIST[[newPatternToIndex, NIL]]; FOR cti: LIST OF ContainerItem _ lowi.first.containersToIndex, cti.rest UNTIL cti = NIL OR ~keepTrying DO subDIDs: LIST OF DID _ NIL; didsInContainer: LIST OF DID _ NIL; success: BOOL _ FALSE; IF cti.first.done THEN LOOP; transID _ YggTransaction.CreateTrans[YggEnvironment.nullTransID]; containerDoc _ YggDIDMap.OpenDocumentFromDID[cti.first.did, transID]; [subDIDs, success] _ YggNav.GetContainersInContainer[transID, cti.first.did, TRUE]; IF ~success THEN { outcome _ YggTransaction.Finish[transID: transID, requestedOutcome: abort]; keepTrying _ FALSE; EXIT; }; FOR dids: LIST OF DID _ subDIDs, dids.rest UNTIL dids = NIL DO lastKnownSubs: LIST OF ContainerItem _ NIL; FOR knownSubs: LIST OF ContainerItem _ lowi.first.containersToIndex, knownSubs.rest UNTIL knownSubs = NIL DO lastKnownSubs _ knownSubs; IF YggDID.EqualDIDs[knownSubs.first.did, dids.first] THEN EXIT; REPEAT FINISHED => { lastKnownSubs _ CONS[[dids.first, FALSE], lastKnownSubs]; }; ENDLOOP; ENDLOOP; [dids: didsInContainer, success: success] _ YggNav.GetObjectsInContainer[trans: transID, containerDID: cti.first.did, dontWait: TRUE]; IF ~success THEN { outcome _ YggTransaction.Finish[transID: transID, requestedOutcome: abort]; keepTrying _ FALSE; RETURN; }; outcome _ YggTransaction.Finish[transID: transID, requestedOutcome: commit]; FOR nowDID: LIST OF DID _ didsInContainer, nowDID.rest UNTIL nowDID = NIL DO skipDID: BOOL _ FALSE; addedValueForDID: BOOL _ FALSE; properties: LIST OF YggRep.Attribute _ NIL; transID _ YggTransaction.CreateTrans[YggEnvironment.nullTransID]; [properties: properties] _ YggNav.GetAllProperties[trans: transID, did: nowDID.first]; outcome _ YggTransaction.Finish[transID: transID, requestedOutcome: commit]; FOR loa: LIST OF YggRep.Attribute _ properties, loa.rest UNTIL loa = NIL OR skipDID DO -- for each attribute IF Rope.Match[loa.first.attributeName, newPatternToIndex] THEN { FOR apdItem: LIST OF AttrDIDVal _ lowi.first.attributePatternDIDs.first.attributes, apdItem.rest UNTIL apdItem = NIL OR skipDID DO -- look for the attribute name under the pattern IF Rope.Equal[apdItem.first.attributeName, loa.first.attributeName] THEN { FOR didOnList: LIST OF DIDVal _ apdItem.first.didsAndValues, didOnList.rest UNTIL didOnList = NIL DO IF YggDID.EqualDIDs[didOnList.first.did, nowDID.first] THEN { IF addedValueForDID THEN { didOnList.first.values _ CONS[loa.first.value, didOnList.first.values]; } ELSE { skipDID _ TRUE; EXIT; }; }; REPEAT FINISHED => { addedValueForDID _ TRUE; apdItem.first.didsAndValues _ CONS[[nowDID.first, LIST[loa.first.value]], apdItem.first.didsAndValues]; }; ENDLOOP; EXIT; }; REPEAT FINISHED => IF ~skipDID THEN { -- attribute name not listed under pattern addedValueForDID _ TRUE; lowi.first.attributePatternDIDs.first.attributes _ CONS[[loa.first.attributeName, LIST[[nowDID.first, LIST[loa.first.value]]]], lowi.first.attributePatternDIDs.first.attributes]; }; ENDLOOP; }; ENDLOOP; ENDLOOP; cti.first.done _ TRUE; ENDLOOP; IF ~ keepTrying THEN EXIT; { FOR apdItem: LIST OF AttrDIDVal _ lowi.first.attributePatternDIDs.first.attributes, apdItem.rest UNTIL apdItem = NIL DO indexFile: YggInternal.FileHandle; containerDoc: Document; atomForIndex: ATOM; componentFiles: LIST OF YggInternal.FileHandle; transID _ YggTransaction.CreateTrans[YggEnvironment.nullTransID]; containerDoc _ YggDIDMap.OpenDocumentFromDID[lowi.first.did, transID]; componentFiles _ YggDIDMap.GetComponentFiles[containerDoc]; atomForIndex _ Atom.MakeAtom[Rope.Concat[YggFixedNames.IndexPrefix, apdItem.first.attributeName]]; indexFile _ YggFile.FileFromComponentFiles[componentFiles: componentFiles, fileUse: atomForIndex]; FOR didsToIndex: LIST OF DIDVal _ apdItem.first.didsAndValues, didsToIndex.rest UNTIL didsToIndex = NIL DO [keepTrying: keepTrying, outcome: outcome, didSomething: didSomething] _ doOneChange [ transID: transID, doc: containerDoc, indexFile: indexFile, attributeName: apdItem.first.attributeName, atomForIndex: atomForIndex, containerDID: lowi.first.did, indexedDID: didsToIndex.first.did, oldValues: NIL, newValues: didsToIndex.first.values]; IF ~keepTrying THEN EXIT; IF didSomething AND indexFile = NIL THEN { indexFile _ YggFile.FileFromComponentFiles[componentFiles: componentFiles, fileUse: atomForIndex]; }; ENDLOOP; REPEAT FINISHED => { -- container processed OK !!! }; ENDLOOP; }; IF ~ keepTrying THEN EXIT; ENDLOOP; }; doDeleteIndexChange: PROC [lowi: LIST OF WorkItem, vDoc: YggRep.VDoc] RETURNS [doRemoveEntry: BOOL _ FALSE, somethingChanged: BOOL _ FALSE] ~ { keepTrying: BOOL _ TRUE; didSomething: BOOL _ TRUE; outcome: YggTransaction.Outcome; containerDID: DID _ lowi.first.did; componentFiles: LIST OF YggInternal.FileHandle; transID: YggEnvironment.TransID; containerDoc: Document; WHILE keepTrying DO success: BOOL _ FALSE; patterns: LIST OF ROPE; fileUses: LIST OF ATOM _ NIL; transID _ YggTransaction.CreateTrans[YggEnvironment.nullTransID]; [patterns, success] _ YggNav.GetPatternsInContainer[containerDID, TRUE]; IF ~success THEN { outcome _ YggTransaction.Finish[transID: transID, requestedOutcome: abort]; keepTrying _ FALSE; RETURN; }; containerDoc _ YggDIDMap.OpenDocumentFromDID[containerDID, transID]; componentFiles _ YggDIDMap.GetComponentFiles[containerDoc]; fileUses _ YggFile.FileUseFromComponentFiles[componentFiles]; FOR locf: LIST OF ATOM _ fileUses, locf.rest UNTIL locf = NIL DO useName: ROPE _ Atom.GetPName[locf.first]; matchName: ROPE _ NIL; IF Rope.Equal[YggFixedNames.IndexPrefix, Rope.Substr[useName, 0, YggFixedNames.IndexPrefixSize]] THEN matchName _ Rope.Substr[useName, YggFixedNames.IndexPrefixSize] ELSE LOOP; FOR lop: LIST OF ROPE _ patterns, lop.rest UNTIL lop = NIL DO IF Rope.Match[pattern: lop.first, object: matchName] THEN EXIT; REPEAT FINISHED => { indexFile: YggInternal.FileHandle _ NIL; indexFile _ YggFile.FileFromComponentFiles[componentFiles: componentFiles, fileUse: locf.first]; YggDIDMap.RemoveComponentFile[containerDoc, indexFile, transID]; YggFile.Delete[file: indexFile, tid: transID]; }; ENDLOOP; ENDLOOP; outcome _ YggTransaction.Finish[transID: transID, requestedOutcome: abort]; IF outcome = commit THEN { somethingChanged _ TRUE; doRemoveEntry_TRUE; keepTrying _ FALSE; }; ENDLOOP; }; doOneChange: PROC [transID: YggEnvironment.TransID, doc: Document, indexFile: YggInternal.FileHandle, attributeName: ROPE, atomForIndex: ATOM, containerDID: DID, indexedDID: DID, oldValues: LIST OF LIST OF YggRep.AttributeValue, newValues: LIST OF LIST OF YggRep.AttributeValue] RETURNS [keepTrying: BOOL _ TRUE, outcome: YggTransaction.Outcome _ active, didSomething: BOOL _ FALSE] ~ { IF indexFile = NIL THEN { -- no index yet, but should there be? success: BOOL _ FALSE; patterns: LIST OF ROPE; shouldBeIndexed: BOOL _ FALSE; [patterns, success] _ YggNav.GetPatternsInContainer[containerDID, TRUE]; IF ~success THEN { outcome _ YggTransaction.Finish[transID: transID, requestedOutcome: abort]; keepTrying _ FALSE; RETURN; }; FOR lor: LIST OF ROPE _ patterns, lor.rest UNTIL lor= NIL DO IF Rope.Match[pattern: lor.first, object: attributeName] THEN { shouldBeIndexed _ TRUE; RETURN; }; ENDLOOP; IF shouldBeIndexed THEN { componentFiles: LIST OF YggInternal.FileHandle; componentFiles _ YggDIDMap.GetComponentFiles[doc]; indexFile _ YggFile.FileFromComponentFiles[componentFiles: componentFiles, fileUse: atomForIndex]; IF indexFile = NIL THEN { indexFile _ YggFile.Create[size: 32, did: containerDID, fileUse: atomForIndex, nearToDid: containerDID, tid: transID]; YggDIDMap.AddComponentFile[doc, indexFile, transID]; }; }; }; IF indexFile # NIL THEN { index: YggIndex.Index; index _ YggIndex.Open[did: containerDID, fileUse: atomForIndex, cacheSize: 8, initialize: FALSE]; FOR loloav: LIST OF LIST OF YggRep.AttributeValue _ oldValues, loloav.rest UNTIL loloav = NIL DO FOR listoav: LIST OF YggRep.AttributeValue _ loloav.first, listoav.rest UNTIL listoav = NIL DO FOR valueSet: LIST OF YggRep.TypedPrimitiveElement _ listoav.first.valueSet, valueSet.rest UNTIL valueSet = NIL DO enumProc: YggIndex.EntryProc ~ { tpesToDelete _ CONS[value, tpesToDelete] }; tpesToDelete: LIST OF YggRep.TypedPrimitiveElement _ NIL; YggIndex.EnumerateEntries[index: index, start: valueSet.first, end: valueSet.first, proc: enumProc]; FOR tpes: LIST OF YggRep.TypedPrimitiveElement _ tpesToDelete, tpes.rest UNTIL tpes = NIL DO didSomething _ TRUE; [] _ YggIndex.DeleteEntry[index: index, value: tpes.first, did: indexedDID, trans: transID]; ENDLOOP; ENDLOOP; ENDLOOP; ENDLOOP; FOR loloav: LIST OF LIST OF YggRep.AttributeValue _ newValues, loloav.rest UNTIL loloav = NIL DO FOR listoav: LIST OF YggRep.AttributeValue _ loloav.first, listoav.rest UNTIL listoav = NIL DO FOR valueSet: LIST OF YggRep.TypedPrimitiveElement _ listoav.first.valueSet, valueSet.rest UNTIL valueSet = NIL DO didSomething _ TRUE; [] _ YggIndex.WriteEntry[index: index, value: valueSet.first, did: indexedDID, trans: transID]; ENDLOOP; ENDLOOP; ENDLOOP; }; }; getContainersToApply: PROC [seedVDoc: YggRep.VDoc, lowi: LIST OF WorkItem, attrToFollow: ROPE _ YggFixedNames.Parents] ~ { parents: LIST OF YggRep.AttributeValue _ NIL; FOR loa: LIST OF YggRep.Attribute _ seedVDoc.metaAttributes, loa.rest UNTIL loa = NIL DO IF Rope.Equal[loa.first.attributeName, attrToFollow] THEN {parents _ loa.first.value; EXIT}; ENDLOOP; IF parents # NIL THEN { FOR parentListAsAttr: LIST OF YggRep.AttributeValue _ parents, parentListAsAttr.rest UNTIL parentListAsAttr = NIL DO FOR parentVS: LIST OF YggRep.TypedPrimitiveElement _ parentListAsAttr.first.valueSet, parentVS.rest UNTIL parentVS = NIL DO IF parentVS.first.docType # YggRep.did THEN LOOP; -- junk lowi.first.containersToApply _ CONS[[NARROW[parentVS.first.bits, DID], FALSE], lowi.first.containersToApply]; ENDLOOP; ENDLOOP; }; }; checkTimeStamps: PROC [lowi: LIST OF WorkItem, timeStamp: YggRep.TimeStamp] RETURNS [ok: BOOL _ TRUE] ~ { doc: Document; doc _ YggDIDMap.OpenDocumentFromDID[lowi.first.did, YggEnvironment.nullTransID]; IF YggRep.CompareTimeStamps[doc.timeStampForIndexMaint, timeStamp] = greater THEN { ok _ FALSE; }; IF ok THEN FOR parentContainers: LIST OF ContainerItem _ lowi.first.containersToApply, parentContainers.rest UNTIL parentContainers = NIL DO doc: Document; doc _ YggDIDMap.OpenDocumentFromDID[parentContainers.first.did, YggEnvironment.nullTransID]; IF YggRep.CompareTimeStamps[doc.timeStampForIndexMaint, timeStamp] = greater THEN { ok _ FALSE; EXIT; }; ENDLOOP; }; addSuperDIDs: PROC [superDIDs: LIST OF DID _ NIL, lowi: LIST OF WorkItem] ~ { lastPC: LIST OF ContainerItem _ NIL; FOR pC: LIST OF ContainerItem _ lowi.first.containersToApply, pC.rest UNTIL pC = NIL DO lastPC _ pC; ENDLOOP; FOR lod: LIST OF DID _ superDIDs, lod.rest UNTIL lod = NIL DO FOR pC: LIST OF ContainerItem _ lowi.first.containersToApply, pC.rest UNTIL pC = NIL DO IF YggDID.EqualDIDs[pC.first.did, lod.first] THEN EXIT; REPEAT FINISHED => { lastPC _ CONS[[lod.first, FALSE], lastPC]; }; ENDLOOP; ENDLOOP; }; debumpContainerUse: PROC [containerDID: YggDID.DID] ~ { val: REF; val _ RedBlackTree.Lookup[self: containerLock, lookupKey: containerDID]; IF val # NIL THEN { entry: containerLockEntry; entry _ NARROW[val]; entry.users _ entry.users - 1; IF entry.users = 0 THEN { [] _ RedBlackTree.Delete[self: containerLock, deleteKey: containerDID]; numLockedContainers _ numLockedContainers - 1; IF numLockedContainers < 0 THEN ERROR; }; } ELSE { ERROR; }; }; bumpContainerUse: INTERNAL PROC [containerDID: YggDID.DID] ~ { val: REF; val _ RedBlackTree.Lookup[self: containerLock, lookupKey: containerDID]; IF val # NIL THEN { entry: containerLockEntry; entry _ NARROW[val]; entry.users _ entry.users + 1; } ELSE { numLockedContainers _ numLockedContainers + 1; RedBlackTree.Insert[self: containerLock, insertKey: containerDID, dataToInsert: NEW[containerLockRep _ [containerDID, 1]]]; }; }; GetKeyProc: RedBlackTree.GetKey = { entry: containerLockEntry _ NARROW[data]; RETURN[ entry.did ]; }; CompareProc: RedBlackTree.Compare = { entryData: containerLockEntry _ NARROW[data]; keyData: DID _ NARROW[k]; SELECT keyData.didHigh FROM > entryData.did.didHigh => RETURN [greater]; < entryData.did.didHigh => RETURN [less]; ENDCASE => { SELECT keyData.didLow FROM > entryData.did.didLow => RETURN [greater]; < entryData.did.didLow => RETURN [less]; ENDCASE => RETURN [equal]; }; }; NextTimeStamp: ENTRY PROC RETURNS [cts: YggRep.TimeStamp] _ { currentTimeStamp _ YggRep.NextTimeStamp[currentTimeStamp]; }; containerLock _ RedBlackTree.Create[getKey: GetKeyProc, compare: CompareProc]; TRUSTED { Process.SetTimeout[condition: @MyCondition, ticks: Process.MsecToTicks[50]]; }; [] _ NextTimeStamp[]; END. JYggIndexMaintImpl.mesa Copyright ำ 1988, 1989 by Xerox Corporation. All rights reserved. Bob Hagmann February 10, 1989 2:16:01 pm PST This module maintains the indices. Maintenance of the indices is delayed. As each transaction commits, changes to properties, changes to the containment hierarchy (and hence the side effect of what is indexed where), and indexing requests (the patterns of properties to be indexed in a container) are sent to this module. For changes to properties or containment, these calls are done during the precommit phase of the transaction. If the system stays up, the commit status of the transaction is later told to this module. Indexing requests calls are done "without" transactions: this module fabricates the transaction. A list of things to do is constructed (WorkToDoList). This list has all changes: changes to properties, changes to the containment hierarchy, and indexing changes. The WorkToDoList is a list of DID's that have been changed. The requests are appended to the end of some part of the WorkToDoList. If this is for an unknown DID, then a new DID item (LIST OF WorkItem) is built. Otherwise, the proper DID item (LIST OF WorkItem) is appended to. A single process looks at the WorkToDoList. It scans for work to do. It does all of the properties, containment hierarchy, and indexing requests. If a latch cannot be obtained, the next item is tried. Apart from doing the updates sequentially, no other order in imposed on the updates. Using some anomolies can occur. The invariant that we wish to enforce is that the object is always indexed in at least all of the places required. Spurious extra indexing is OK as it should be quite infrequent and the indices are just supposed to be hints anyway (and we could write code to clean up the indicies periodically). To do: 1) Only index the "primary" containers 2) Stabilize the work to do list. Recover it after crash recovery and start up. Write the list into the contents of the object with DID YggDIDPrivate.LowForIndexMaint. Only work items and their completion must be recoverable: the intermediate state of an index change can be redone (possibly with no change). Use the contents to keep variable sized "WorkItem" records. Keep four (or more) free lists. To allocate, keep a rover and take the next list that is not locked. Lock the list, with unlock during commit/abort notification. If the update commits, add it to the WorkToDoList, with no stable storage update. When an update is done, remove it by trying to add it to the following record, if the following record is free and its list is unlocked. Don't do stable updates to indicate progress in indx updates: just redo the whole update after recovery. Periodically, when all of the lists are empty, reinitialize all of the lists (don't reinitialize again until an update is seen). Header: free list start pointers Records: 0) size Used 1) free? if so, which list 2) sequence number (used when reconstructing lists) 3.. n-1) all the bits n) size Used (if backward parsing of the lists is needed) 3) Rationalize the use of transactions Types, global data, and constants Exported procedures Note that this did/attributeName has a new value. Note that this did/metaattribute has a new value. Called during Pre-Commit. Note state of previous NewValueForAttribute or NewValueForMetaAttribute call(s). Called during Commit or Abort. Note that this container now has different indexing. Utilities The process that does all the work Flip through the work to do list for a DID process update type procs add/remove DID for WorkItem to this parent: do nothing; the next clause will handle it. add/remove DID for WorkItem as subcontainer to this parent: do nothing; the next clause will handle it. add/remove DID as subcontainer to this parent (in WorkItem) 1) look up all super containers that have to be indexed 2) build a list of attribute patterns to index 3) scan container and all its children containers; for each attribute that is indexed remember the did of the object 4) for each super container, index matched attributes 5) check to be sure nothing changed in the super container DG; on change GO TO 1 1) look up all super containers that have to be indexed 2) build a list of attribute patterns to index 3) scan container and all its children containers; for each attribute that is indexed remember the did of the object 4) for each super container, index matched attributes 5) check to be sure nothing changed in the super container DG; on change GO TO 1 Follow this algorithm: 1) build up list of containers to index 2) build up list of attributes/dids 3) apply to index It should not be necessary to check time stamps: any changes will be handled by other update 4) check container time stamps 5) if a problem, go to 1 1) build up list of containers to index 2) build up list of attributes/dids 3) apply to index Using the supplied transID, modify an index (if needed) at the specified doc/indexFile/containerDID. The index to be changed is specified by attributeName/atomForIndex. Valued to be removed are in oldValues, while new values to index are in newValues. The object to be indexed has a did of indexedDID. Transaction failures are reported in outcome with the keepTrying set to FALSE. clean out old stuff PROC [value: YggRep.TypedPrimitiveElement, did: YggDID.DID] RETURNS [continue: BOOL]; inset new stuff code before it was procedure-ized IF indexFile = NIL THEN { -- no index yet, but should there be? patterns: LIST OF ROPE; shouldBeIndexed: BOOL _ FALSE; [patterns, success] _ YggNav.GetPatternsInContainer[parentContainers.first.did, TRUE]; IF ~success THEN { outcome _ YggTransaction.Finish[transID: transID, requestedOutcome: abort]; keepTrying _ FALSE; EXIT; }; FOR lor: LIST OF ROPE _ patterns, lor.rest UNTIL lor= NIL DO IF Rope.Match[pattern: lor.first, object: lowi.first.attributeOrPattern] THEN { shouldBeIndexed _ TRUE; EXIT; }; ENDLOOP; IF shouldBeIndexed THEN { indexFile _ YggFile.Create[size: 32, did: parentContainers.first.did, fileUse: atomForIndex, nearToDid: parentContainers.first.did, tid: transID]; YggDIDMap.AddComponentFile[doc, indexFile, transID]; }; }; IF indexFile # NIL THEN { index: YggIndex.Index; index _ YggIndex.Open[did: parentContainers.first.did, fileUse: atomForIndex, cacheSize: 8, initialize: FALSE]; clean out old stuff FOR loloav: LIST OF LIST OF YggRep.AttributeValue _ lowi.first.oldValues, loloav.rest UNTIL loloav = NIL DO FOR listoav: LIST OF YggRep.AttributeValue _ loloav.first, listoav.rest UNTIL listoav = NIL DO FOR valueSet: LIST OF YggRep.TypedPrimitiveElement _ listoav.first.valueSet, valueSet.rest UNTIL valueSet = NIL DO enumProc: YggIndex.EntryProc ~ { PROC [value: YggRep.TypedPrimitiveElement, did: YggDID.DID] RETURNS [continue: BOOL]; tpesToDelete _ CONS[value, tpesToDelete] }; tpesToDelete: LIST OF YggRep.TypedPrimitiveElement _ NIL; YggIndex.EnumerateEntries[index: index, start: valueSet.first, end: valueSet.first, proc: enumProc]; FOR tpes: LIST OF YggRep.TypedPrimitiveElement _ tpesToDelete, tpes.rest UNTIL tpes = NIL DO [] _ YggIndex.DeleteEntry[index: index, value: tpes.first, did: lowi.first.did]; ENDLOOP; ENDLOOP; ENDLOOP; ENDLOOP; inset new stuff FOR loloav: LIST OF LIST OF YggRep.AttributeValue _ lowi.first.newValues, loloav.rest UNTIL loloav = NIL DO FOR listoav: LIST OF YggRep.AttributeValue _ loloav.first, listoav.rest UNTIL listoav = NIL DO FOR valueSet: LIST OF YggRep.TypedPrimitiveElement _ listoav.first.valueSet, valueSet.rest UNTIL valueSet = NIL DO YggIndex.WriteEntry[index: index, value: valueSet.first, did: lowi.first.did]; ENDLOOP; ENDLOOP; ENDLOOP; }; append supers to list of stuff to do, provided that they have not yet been seen red black procs PROC [data: UserData] RETURNS [Key] PROC [k: Key, data: UserData] RETURNS [Basics.Comparison] Initialization Initialization Uncomment the next two statements when ready Process.Detach[FORK IndexMaintProcess]; DoingIndexMaint _ TRUE; ส/๚˜codešœ™KšœB™BK™,—K™K™Kšœ"™"K™K™šฯk ˜ Kšœœ˜ Kšœœ˜Kšœœ)˜6Jšœ œ:˜LKšœœœ˜0Kšœœœ ˜Kšœ œQ˜`Kšœœ˜/Kšœœ ˜Kšœœ˜,KšœœE˜RKšœœV˜iKšœ œE˜SKšœœ˜Kšœ œ˜Kšœœq˜}Kšœœฌ˜ธKšœœ,˜@—K˜Kšะblœœ˜ Kšœw˜~Kšœ#˜*šœ˜Icode0™IbodyšœŸ™ŸMšœซ™ซMš œ฿œœ6œœ™ฝMšœห™หMšœลฯiœา™ŸM™šž™Mšž&™&šžท™ทMšœฉ™ฉ™Iitem™—™N™ N™N™3N™N™9——Mšž&™&—N™—head™!Lšœœœ˜L˜Kšœ œœ ˜!Kšœ œœ ˜8K˜Kšœœœœ˜Kšœœœ˜+L˜Lšœœœœ˜$L˜Lšœœœ˜L˜Lšœ  œ˜L˜Lšœ œ5˜EJ˜L˜:L˜šœ œœ˜Lšœœ˜ Lšœ˜Lšœ/˜/L˜Lšœœ˜Lš œ œœœœ˜1Lš œ œœœœ˜1Lšœœœ˜7Lšœœœ˜)L˜6Lšœœœ˜)Lšœœœ˜.L˜—šœœœ˜Lšœœ˜ Lšœœ˜Lšœœ ˜(L˜—šœœœ˜Lšœ œœ ˜L˜—Lšœœœœ˜,šœ œœ˜Lšœœ˜Lšœ œœ ˜L˜—šœ œœ˜Lšœœ˜Lšœœœ ˜#L˜—šœœœ˜Lšœœ˜ Lšœœœœ˜3L˜—L˜Kšœ"˜"K˜Kšœœ˜K˜Kšœœœ˜0šœœœ˜!Lšœœ˜ Kšœœ˜K˜—K˜L˜K˜—šœ™L˜šฯnœœœœ$œ œœœœ#œœœœ˜รKšœ1™1KšœF˜FK˜K˜—š  œ œœ,œœ˜†KšœL™LJšœK˜KK˜K™—š  œ œœœ˜fKšœp™pKšœ˜K˜K™—š œœœœ œœœ œ˜vKšœ4™4Jšœ9˜9K˜—K˜K˜—™ code2šฯbœœœ œ$œ œœœœ#œœœœ˜รPšœ œœœ˜(Pšœœœ˜ š œœœ+œœ˜Ošœ7œ˜?Pšœœœ œ˜š œœœ,œœ˜PPšœ˜Pšœ˜—Pš œœGœœœ˜iPšœ˜P˜—Pšœ˜šœœ˜Pš œœœœœGœ˜xPšœ œœ˜)Pšœœ˜P˜—Pšœ˜—Pšœ ˜P˜—š กœœœœ œœ˜`Pšœ œœœ˜(Pšœœœ˜ š œœœ+œœ˜Ošœ@œ˜HPšœœœ œ˜š œœœ,œœ˜PPšœ˜Pšœ˜—Pšœœ4œœ œœœœœœ˜Pšœ˜P˜—Pšœ˜šœœ˜Pšœœœœœ4œœ œœœœœ˜คPšœ œœ˜)Pšœœ˜P˜—Pšœ˜—Pšœ˜Pšœ ˜P˜—š ก)œœœ œ,œœ˜•Pšœ œœœ˜(Pšœœœ˜ š œœœ+œœ˜Ošœ7œ˜?Pšœœœ œ˜š œœœ,œœ˜PPšœ˜Pšœ˜—Pš œœ"œœœœœ˜ePšœ˜P˜—Pšœ˜šœœ˜Pšœœœœœ"œœœœ˜yPšœ œœ˜)Pšœœ˜P˜—Pšœ˜—Pšœ˜Pšœ ˜P˜—š ก œœœœœ˜OPšœœ˜Pšœœœ˜Pšœœœ˜ š œœœ+œœ˜Ošœ-œ˜5š œœœ,œœ˜Pšœ0œ˜8Jšœ"œ œœ˜EJšœ œ˜P˜—Pšœ˜—Pšœ˜P˜—šœœ˜Pšœ˜P˜—Pšœ˜—Pšœ œœ˜%Pšœ œ˜(Pšœ ˜P˜——™"š œœ˜šœฯc ˜šœ œœ˜Pšœ ˜P˜—Pšœœœ˜Pšœ œœœ˜'š œœœ+œœ˜OP™ Pšœ œœ˜Pšœœœ˜Pšœœœœ˜-šก œœœ˜Pšœœœ-ข*˜ršœœข˜(šœ œœข˜0šœœœขE˜fPšœ˜P˜—šœœขd˜lPšœ1˜1P˜—P˜—šœœ˜šœœœขI˜jPšœ˜P˜—šœœขA˜JPšœ1˜1P˜—P˜—P˜—P˜—Pšœœ˜Pšœhœ˜nPš œœœœ œœข(˜[Pšœœ˜š œœœ"œœ˜FPšœ™ Pšœœœ˜Pšœœœ˜Pšœ+œœข+˜dPšœ)œœข(˜pšœœ˜"˜Pšœ>˜>P˜—šœ ˜ PšœD˜DP˜—šœ˜PšœD˜DP˜—šœ˜Pšœ=˜=P˜—Pšœœ˜—šœœ˜Pšœœ˜Pšœ˜Pšœœ˜P˜—Pšœœ˜+Pšœ˜Pšœข ˜—Pšœข#˜-—Pšœœ˜%Pšœข ˜—Pšœ˜——™šก œœœœœœœœœ˜ŠPšœ œœ˜Pšœ œœ:ข!˜…šœ ˜š œœœEœœ˜Pšœ˜Pšœœ˜Pšœ ˜ Kšœœœ˜/Kšœ ˜ Kš œ œœœœ˜Kšœ œœ˜Kšœ"˜"Kšœœœข˜:KšœA˜AKšœI˜IKšœc˜cKšœ2˜2Pšœd˜dPšœc˜cPšœVœ˜\šœ œ˜PšœK˜KPšœ œ˜Pšœ˜P˜—Pšœฝ˜ฝPšœ œœ˜PšœL˜Lšœœ˜Pšœœ˜Pšœœ˜#Pšœ œœ!ขR˜ŠP˜—šœœ˜Pšœ œ˜Pšœ˜P˜—Pšœ œœ˜šœœข˜4Pšœœœ˜Pšœ4˜4šœœ˜ Pšœœ˜Pšœœ˜Pšœ œ˜P˜—šœœขO˜Xš œœœEœœ˜Pšœœ˜$Pšœ˜—P˜—P˜—Pšœ˜—Pšœ˜—P˜—šก œœœœœœœœœ˜‰š œœœFœœœ˜išœœ˜šœ@˜@Pšœ œI™WPšœœ˜Pšœœ˜P˜—šœA˜APšข7˜7Pšœ œœ˜Pšœœœ˜Pšœ ˜ Pšœœ˜Pšœ ˜ Pšœ˜Pšœ˜Pšœ"˜"Pšœiœ˜oPš œœœœœœข˜FPšœ œœ4œ˜fP˜šœ ˜š œœœEœœ˜Kš œ œœœœ˜Kšœ œœ˜Kšœœœข˜:KšœA˜AKšœR˜RKšœl˜lPšœVœ˜\šœ œ˜PšœK˜KPšœ œ˜Pšœ˜P˜—š œœœ-œœ˜MKšœœ˜-Kšœ‡œณœ œœœœœœ˜ฒKšœ œœ˜šœœ˜PšœL˜Lšœœ˜Pšœœ˜Kšœ œœB˜WP˜—šœœ˜Pšœ œ˜Pšœ˜P˜—K˜—šœœข˜2Pšœœ˜#Pšœ œœขS˜‰P˜—Kšœ˜—PšœK˜KPšœ œœ˜šœœข˜4Pšœœœ˜Pšœ4˜4šœœ˜ Pšœœ˜Pšœœ˜Pšœ œ˜P˜—šœœขO˜Xš œœœEœœ˜Pšœœ˜$Pšœ˜—P˜—P˜—Pšœ˜—Pšœ˜—P˜—šœI˜IPšœ œY™gPšœœ˜Pšœœ˜P˜—šœH˜Hšข:œ™;P™7P™.P™tP™5P™P—Pšœ œœ˜Pšœœœ˜Pšœ ˜ Pšœœ˜Pšœ ˜ šœ˜Pšก7™7Pšก.™.—Pšœ"˜"šœ ˜šœ œœ˜,Pšœœœ˜-PšœKœ˜SKšœA˜Aš œœœ8œœ˜ZKš œ œœœœ˜Kšœ œœ˜Pšœ œœœ˜KšœE˜EKšœl˜lPšœIœ˜Ošœ œ˜PšœK˜KPšœ œ˜Pšœ˜P˜—š œœœœœœ˜@š œœœ@œœ˜rPšœ5œœ˜Ašœœ˜Pšœœœ˜IP˜—Pšœ˜—Pšœ˜—PšœCœ˜Išœ œ˜Pšœ œ˜Pšœ˜P˜—Pšœ*˜*š œœœœœœ˜=š œœœ:œ œ˜`Pšœ9œœ˜Ešœœ˜Pšœ"œ$œ$˜qP˜—Pšœ˜—Pšœ˜—Pšœ˜—šœœ˜Pšœœ˜#Pšœœœข˜*P˜—PšœL˜LP˜Pšกt™t—Pšœ œœข˜1Pš œ œœ œœ˜h˜š œœœ8œœœ ˜iKš œ œœœœ˜Kš œœœœœ˜#Kšœ œœ˜Kšœœœ˜KšœA˜AKšœE˜EPšœMœ˜Sšœ œ˜PšœK˜KPšœ œ˜Pšœ˜P˜—š œœœœœœ˜>Pšœœœœ˜+š œ œœ>œ œ˜lPšœ˜Pšœ3œœ˜?šœœ˜Pšœœœ˜9P˜—Pšœ˜—Pšœ˜—Pšœ†˜†šœ œ˜PšœK˜KPšœ œ˜Pšœ˜P˜—PšœL˜Lš œ œœœ œ œ˜LKšœ œœ˜Kšœœœ˜Kšœ œœœ˜+KšœA˜AKšœV˜VPšœL˜Lšœœœ)œœœ œข˜mš œœœ8œœœ ˜ešœEœข%˜tšœ œœ1œ œœ œข0˜˜šœBœ˜Jš œ œœ6œ œ˜dšœ5œ˜=šœœ˜Kšœœ*˜GK˜—šœœ˜Kšœ œ˜Kšœ˜Kšœ˜—Kšœ˜—šœœ˜Kšœœ˜Kšœœœ1˜gK˜—Kšœ˜—Kšœ˜K˜—š œœœ œข+˜RKšœœ˜Kšœœœœ,˜zK˜—Kšœ˜—K˜—Kšœ˜—Kšœ˜—Kšœ˜—Pšœœ˜Pšœ˜—P˜Pšก5™5—Pšœœœ˜˜š œœœ8œœ˜ZPšœœœ˜š œœœœ,œœ˜Sš œœœ8œœ˜Zšœ7œ˜?š œ œœ1œ œ˜[Kšœ"˜"Pšœ˜Pšœœ˜Kšœœœ˜/KšœA˜AKšœE˜EKšœ;˜;Pšœb˜bPšœc˜cš œœœ8œœ˜jKšœฅœœœœ&œœœœ˜ขKšœ œœ˜šœœ œœ˜*Pšœc˜cP˜—Pšœ˜—šœœข˜2Pšœœ˜P˜—K˜Kšœ˜—Kšœ˜K˜—Pšœœœ˜Pšœ˜—Pšœ˜—Pšœ˜—P˜PšกP™P—Pšœœœ˜˜Pšœœœ˜Pšœ4˜4š œ œœœDœœ˜‰Pšœ˜Kšœ[˜[šœYœ˜aKšœœ˜ Kšœ˜K˜—Kšœ˜—šœœ˜ Pšœœ˜Pšœœ˜Pšœ œ˜P˜—šœœขm˜vPšœ#˜#Pšœ#˜#P˜—P˜—Pšœ˜—P˜—Pšœœ˜—Pšœ˜—P˜—šกœœœœœœœœœ˜™P™'P™#P™—™\Pšฯs™Pšฃ™—Pšœ œœ˜Pšœœœ˜Pšœ ˜ Pšœœ˜#Pšœ ˜ Pšœ˜Pšœ8˜8šœ ˜Kšœ œœ˜Pšœ œœœ˜Pš œ œœ œœ˜hPš œ#œœ#œœ˜oš œœœ8œœœ ˜iKš œ œœœœ˜Kš œœœœœ˜#Kšœ œœ˜Kšœœœ˜KšœA˜AšœE˜EPšก'™'—PšœMœ˜Sšœ œ˜PšœK˜KPšœ œ˜Pšœ˜P˜—š œœœœœœ˜>Pšœœœœ˜+š œ œœ>œ œ˜lPšœ˜Pšœ3œœ˜?šœœ˜Pšœœœ˜9P˜—Pšœ˜—Pšœ˜Pšก#™#—Pšœ†˜†šœ œ˜PšœK˜KPšœ œ˜Pšœ˜P˜—PšœL˜Lš œ œœœ œ œ˜LKšœ œœ˜Kšœœœ˜Kšœ œœœ˜+KšœA˜AKšœV˜VPšœL˜Lšœœœ)œœœ œข˜mšœ8œ˜@šœ œœMœ œœ œข0˜ดšœBœ˜Jš œ œœ6œ œ˜dšœ5œ˜=šœœ˜Kšœœ*˜GK˜—šœœ˜Kšœ œ˜Kšœ˜Kšœ˜—Kšœ˜—šœœ˜Kšœœ˜Kšœœœ1˜gK˜—Kšœ˜—Kšœ˜K˜—š œœœ œข+˜RKšœœ˜Kšœ3œœœH˜ฒK˜—Kšœ˜—K˜—Kšœ˜—Kšœ˜—Pšœœ˜Pšœ˜Pšก™—Pšœœœ˜˜š œ œœMœ œ˜wKšœ"˜"Pšœ˜Pšœœ˜Kšœœœ˜/KšœA˜AKšœF˜FKšœ;˜;Pšœb˜bPšœc˜cš œœœ8œœ˜jKšœฆœ'˜ะKšœ œœ˜šœœ œœ˜*Pšœc˜cP˜—Pšœ˜—šœœข˜2P˜—K˜Kšœ˜—P˜—Pšœœœ˜Pšœ˜—P˜—šกœœœœœœœœœ˜Pšœ œœ˜Pšœœœ˜Pšœ ˜ Pšœœ˜#Kšœœœ˜/Pšœ ˜ Pšœ˜šœ ˜Kšœ œœ˜Pšœ œœœ˜Pšœ œœœ˜KšœA˜APšœBœ˜Hšœ œ˜PšœK˜KPšœ œ˜Pšœ˜P˜—KšœD˜DKšœ;˜;Kšœ=˜=š œœœœœœ˜@Kšœ œ˜*Kšœ œœ˜šœ_œA˜ฆKšœœœ˜ —š œœœœœœ˜=Kšœ3œœ˜?šœœ˜Pšœ(˜(Pšœa˜aKšœ@˜@Kšœ.˜.K˜—Kšœ˜—Kšœ˜—PšœK˜Kšœœ˜Pšœœ˜Pšœœ˜Pšœ œ˜P˜—Pšœ˜—P˜—š&ก œœdœœœœ œœœœ#œœœœœœœ:œœ˜ƒPšœฯoœ/คœ+คœค œ#ค œ)ค œ(คœ ค œœ™€šœ œœข%˜@Kšœ œœ˜Pšœ œœœ˜Pšœœœ˜PšœBœ˜Hšœ œ˜PšœK˜KPšœ œ˜Pšœ˜P˜—šœœœœœœœ˜=šœ7œ˜?Pšœœ˜Pšœ˜P˜—Pšœ˜—šœœ˜Kšœœœ˜/Kšœ2˜2Pšœc˜cšœ œœ˜Pšœv˜vPšœ4˜4P˜—Pšœ˜—P˜—šœ œœ˜Pšœ˜šœZœ˜aP™—šœ œœœœก œœ œ˜`š œ œœ4œ œ˜^š œ œœFœ œ˜ršœ ˜ Pšœ3œœ œ™UPšœœ˜(P˜—Pšœœœ œ˜9Pšœd˜dš œœœ8œœ˜\P˜Pšœ\˜\Pšœ˜—Pšœ˜—Pšœ˜—Pšœ˜P™—šœ œœœœก œœ œ˜`š œ œœ4œ œ˜^š œ œœFœ œ˜rP˜Pšœ_˜_Pšœ˜—Pšœ˜—Pšœ˜—P˜—˜™!šœ œœข%™@Pšœ œœœ™Pšœœœ™PšœPœ™Všœ œ™PšœK™KPšœ œ™Pšœ™P™—šœœœœœœœ™=šœGœ™OPšœœ™Pšœ™P™—Pšœ™—šœœ™Pšœ’™’Pšœ4™4Pšœ™—P™—šœ œœ™Pšœ™šœhœ™oP™—šœ œœœœ$ก œœ œ™kš œ œœ4œ œ™^š œ œœFœ œ™ršœ ™ Pšœ3œœ œ™UPšœœ™(P™—Pšœœœ œ™9Pšœd™dš œœœ8œœ™\PšœP™PPšœ™—Pšœ™—Pšœ™—Pšœ™P™—šœ œœœœ$ก œœ œ™kš œ œœ4œ œ™^š œ œœFœ œ™rPšœN™NPšœ™—Pšœ™—Pšœ™—P™—P˜———š  œœœœœ˜{Kšœ œœœ˜-š œœœ6œœ˜XKšœ3œœ˜\Kšœ˜—šœ œœ˜š œœœ8œœ˜tš œ œœOœ œ˜{Pšœ%œœข˜9Pš œœœœœ!˜mPšœ˜—Pšœ˜—P˜—J˜—š  œœœœ5œœ˜jPšœ˜KšœP˜PšœKœ˜SKšœœ˜ K˜—šœœœœœEœœ˜ŒPšœ˜Kšœ\˜\šœKœ˜SKšœœ˜ Kšœ˜K˜—Kšœ˜—K˜—š  œœ œœœœœœ˜NPšขO™OPšœœœœ˜$š œœœ7œœ˜WPšœ ˜ Pšœ˜—š œœœœœœ˜=š œœœ7œœ˜WJšœ+œœ˜7šœœ˜Pšœ œ œ ˜*P˜—Pšœ˜—Pšœ˜—P˜—˜P˜——šœ™š œœœ˜8Kšœœ˜ KšœH˜Hšœœœ˜Kšœ˜Kšœœ˜Kšœ˜šœœ˜KšœG˜GKšœ.˜.Kšœœœ˜&K˜—K˜—šœœ˜Kšœ˜K˜—P˜—š œ œœ˜?Kšœœ˜ KšœH˜Hšœœœ˜Kšœ˜Kšœœ˜Kšœ˜K˜—šœœ˜Kšœ.˜.KšœPœ(˜{K˜—P˜—š  œ˜&Jšœœ™#Jšœœ˜)Jšœ˜J˜—•StartOfExpansion[]š  œ˜%Jšœœ™9Jšœ œ˜-Jšœ œœ˜šœ˜Jšœœ ˜,Jšœœ˜)šœ˜ šœ˜Jšœœ ˜+Jšœœ˜(Jšœœ ˜—J˜——J˜——™š  œœœœ˜=Kšœ:˜:K˜——™KšœN˜Nšœ˜ Pšž,™,Pšœœ™'P™PšœL˜LPšœ˜—P˜K˜—K˜Kšœ˜—…—‘œเเ