DIRECTORY Atom USING [GetPName, MakeAtom], BasicTime, DBCommon, DBStats USING [Inc, Starting, Stopping], DBStorage, DBDefs, DB, DBModel, DBModelPrivate, DBModelSchema, IO, Rope; DBModelBasicImpl: CEDAR PROGRAM IMPORTS Atom, BasicTime, DBStats, DBStorage, DB, DBModel, DBDefs, DBModelPrivate, DBModelSchema, IO, Rope EXPORTS DB, -- Eq, Null, etc. DBModel, -- "Q" procedures DBModelPrivate = -- surrogate relship procs and GetTypeProp BEGIN OPEN DB, DBCommon, DBDefs, DBModel, DBModelPrivate; fakeTransAbort: BOOL_ FALSE; ROPE: TYPE = Rope.ROPE; QDeclareEntity: PUBLIC PROC[d: Domain, name: ROPE_ NIL, version: Version_ NewOrOld] RETURNS [e: Entity] = BEGIN GenSym: PROC[d: Domain] RETURNS[e: Entity] = BEGIN -- generates a unique anonymous entity ENABLE DB.Error => TRUSTED {IF code=NonUniqueEntityName THEN RETRY}; random: LONG CARDINAL _ LOOPHOLE[BasicTime.GetClockPulses[]]; name: ROPE _ IO.PutR[IO.card[random]]; e _ CreateEntity[d, name]; END; IF QNullDomain[d] THEN ERROR DB.Error[IllegalDomain]; IF DBModelSchema.InvalidDomain[d] THEN ERROR DB.Error[InvalidSchema]; IF d.isSystem THEN ERROR DB.Error[IllegalDomain]; IF name = NIL THEN -- Automatically generate new name/entity if no name given IF version=OldOnly THEN RETURN[NIL] ELSE {e _ GenSym[d]; RETURN}; e _ FetchEntity[d, name]; IF e = NIL THEN IF version#OldOnly THEN e_ CreateEntity[d, name] ELSE NULL ELSE -- e#NIL IF version=NewOnly THEN ERROR DB.Error[AlreadyExists] ELSE RETURN; END; FetchEntity: PROC[d: Domain, name: ROPE] RETURNS [e: Entity] = BEGIN nextE: Entity; es: EntitySet; esi: CARDINAL; IF fakeTransAbort THEN ERROR DB.Aborted[QGetSegmentInfo[d.segment.segment].trans]; IF QDomainEq[d, DomainDomain] THEN SELECT TRUE FROM name.Equal["Domain", FALSE] => RETURN[DomainDomain.tuple]; name.Equal["Relation", FALSE] => RETURN[RelationDomain.tuple]; name.Equal["Attribute", FALSE] => RETURN[AttributeDomain.tuple]; name.Equal["DataType", FALSE] => RETURN[DataTypeDomain.tuple]; name.Equal["Index", FALSE] => RETURN[IndexDomain.tuple]; name.Equal["IndexFactor", FALSE] => RETURN[IndexFactorDomain.tuple]; ENDCASE => NULL; [es, esi] _ GetNewEntitySet[]; es_ QDomainSubset[d: d, lowName: name, es: es]; e_ QNextEntity[es]; nextE_ QNextEntity[es]; QReleaseEntitySet[es]; ReturnEntitySet[esi]; IF NullEntity[e] THEN RETURN[NIL]; IF NOT NullEntity[nextE] THEN ERROR DB.Error[MultipleMatch]; RETURN[e] END; CreateEntity: PROC [d: Domain, name: ROPE] RETURNS [e: Entity] = BEGIN DBStats.Inc[CreateEntity]; e_ DBStorage.CreateTuple[DBModelSchema.GetDomainTuple[d]]; IF name=NIL THEN CreateEntityIndexEntries[e] -- we may disallow this name=NIL case soon ELSE ChangeNameBody[e: e, s: name, eD: d, existingIndexEntry: FALSE ! DB.Error => TRUSTED {IF code=NonUniqueEntityName THEN DBStorage.DestroyTuple[e]} ]; END; StringToValue: PROC[s: ROPE, a: Attribute] RETURNS[Value] = TRUSTED BEGIN pos: INT; SELECT a.type FROM RopeType => RETURN[DB.S2V[s]]; IntType => IF s.Length[]=0 THEN RETURN[DB.I2V[0]] ELSE RETURN[DB.I2V[RopeToInt[s]]]; TimeType => IF s.Length[]=0 THEN RETURN[DB.T2V[LOOPHOLE[LONG[0]]]] ELSE RETURN[DB.T2V[RopeToTime[s]]]; BoolType => IF s.Equal["TRUE"] THEN RETURN[DB.B2V[TRUE]] ELSE IF s.Equal["FALSE"] THEN RETURN[DB.B2V[FALSE]] ELSE ERROR DB.Error[MismatchedAttributeValueType]; AnyDomainType => IF s.Length[]=0 THEN RETURN[[null[]]] ELSE IF (pos_ s.Find[":"])#-1 THEN BEGIN -- treat s as name of an entity in a.type vtd: Domain _ QDeclareDomain[s.Substr[0, pos], a.relation.segment.segment, OldOnly]; IF QNullDomain[vtd] THEN ERROR DB.Error[NotFound]; RETURN[DB.E2V[FetchEntity[vtd, s.Substr[pos+2]]]]; END ELSE -- no "domain: value" specified BEGIN d: Domain; e: Entity; ds: DomainSet; dsi: CARDINAL; [ds, dsi] _ GetNewDomainSet[]; ds _ QEnumerateDomains[segment: a.relation.segment.segment, lowName: "", ds: ds]; UNTIL QNullDomain[d_ QNextDomain[ds]] DO e_ QDeclareEntity[d, s, OldOnly ]; IF e#NIL THEN { QReleaseDomainSet[ds]; ReturnDomainSet[dsi]; RETURN[DB.E2V[e]] }; ENDLOOP; QReleaseDomainSet[ds]; ReturnDomainSet[dsi]; ERROR DB.Error[NotFound]; END; ENDCASE => ERROR DB.Error[MismatchedAttributeValueType] END; RopeToTime: PROC[s: ROPE] RETURNS [gmt: BasicTime.GMT] = BEGIN gmt_ IO.GetTime[IO.RIS[s] ! IO.Error => ERROR DB.Error[MismatchedAttributeValueType]]; END; RopeToInt: PROC[s: ROPE] RETURNS [i: INT] = BEGIN ENABLE IO.Error => ERROR DB.Error[MismatchedAttributeValueType]; i_ IO.GetInt[IO.RIS[s]]; END; IsData: PROC[t: TupleHandle] RETURNS[BOOL] = INLINE BEGIN IF t.tid=MaxVTID] }; END; QDestroyEntity: PUBLIC PROC[e: Entity] = BEGIN DBStats.Starting[DestroyEntity]; IF NullEntity[e] THEN RETURN; IF IsSystem[e] THEN ERROR DB.Error[DictionaryUpdate] ELSE BEGIN DestroyLinksTo[e]; -- This also destroys any surrogate relation index entries DestroyEntityIndexEntries[e]; -- This destroys the name index entry SetValFromHandle[e, defaultNameHandle, RopeType, , nullValue]; -- So DBStorage doesn't crash DBStorage.DestroyTuple[e]; END; DBStats.Stopping[DestroyEntity]; END; QDeclareRelship: PUBLIC PROC[ r: Relation, init: AttributeValueList_ NIL, version: Version_ NewOrOld] RETURNS[t: Relship] = BEGIN privateInit: PrivateAttributeValueList; IF QNullRelation[r] THEN ERROR DB.Error[IllegalRelation]; IF DBModelSchema.InvalidRelation[r] THEN ERROR DB.Error[InvalidSchema]; privateInit _ MakePrivate[init]; IF version=NewOnly OR init=NIL THEN RETURN[CreateRelship[r, privateInit]]; t_ FetchRelship[r, init]; IF t=NIL AND version=NewOrOld THEN RETURN[CreateRelship[r, privateInit]]; END; MakePrivate: PROC[pubList: AttributeValueList] RETURNS[PrivateAttributeValueList] = BEGIN IF pubList = NIL THEN RETURN[NIL] ELSE RETURN[CONS[ [ pubList.first.attribute, [public[pubList.first.lo]], [public[pubList.first.hi]] ], MakePrivate[pubList.rest] ]] END; CreateRelship: PUBLIC PROC[r: Relation, init: PrivateAttributeValueList_ NIL] RETURNS[t: Relship] = BEGIN DBStats.Inc[CreateRelship]; IF r.isSystem THEN ERROR DB.Error[DictionaryUpdate]; IF r.is1to1 THEN RETURN[SurrogateCreateRelship[r, init]]; t_ DBStorage.CreateTuple[DBModelSchema.GetRelationTuple[r]]; IF init#NIL THEN SetMultipleF[t, init ! DB.Error => TRUSTED {IF code=NonUniqueKeyValue THEN DBStorage.DestroyTuple[NARROW[t]]} -- clean up -- ]; CreateAllIndexEntries[t]; END; FetchRelship: PROC[r: Relation, avl: AttributeValueList] RETURNS[t: Relship] = BEGIN nextRel: Relship; rs: RelshipSet; rsi: CARDINAL; [rs, rsi] _ GetNewRelshipSet[]; rs_ QRelationSubset[r, avl, First, rs]; t_ QNextRelship[rs]; nextRel_ QNextRelship[rs]; QReleaseRelshipSet[rs]; ReturnRelshipSet[rsi]; IF nextRel#NIL THEN ERROR DB.Error[MultipleMatch]; -- need this any more? RETURN[t] END; SurrogateCreateRelship: PUBLIC PROC[r: Relation, init: PrivateAttributeValueList] RETURNS[SurrogateRelshipHandle] = BEGIN t: SurrogateRelshipHandle; DBStats.Inc[SurrogateCreateRelship]; t_ NEW[DBDefs.SurrogateRelshipObject]; t.relation_ r; t.targetAttribute_ GetFirstAttribute[r]; IF init#NIL THEN SetMultipleF[t, init]; RETURN[t] END; QDestroyRelship: PUBLIC PROC[t: Relship] = BEGIN DBStats.Starting[DestroyRelship]; WITH t SELECT FROM t1: TupleHandle => { r: Relation; IF NullRelship[t1] THEN { DBStats.Stopping[DestroyRelship]; RETURN }; r _ GetCachedRelshipInfo[SegmentOf[t1], t1]; IF r.isSystem THEN ERROR DB.Error[DictionaryUpdate]; IF r.is1to1 THEN { SurrogateDestroyRelship[t1]; DBStats.Stopping[DestroyRelship]; RETURN }; DestroyAllIndexEntries[t1, r]; -- (1) DestroyVariableFieldsOf[t1, r]; -- (2) DBStorage.DestroyTuple[t1] }; -- (3) t2: SurrogateRelshipHandle => SurrogateDestroyRelship[t2]; ENDCASE => ERROR DB.Error[IllegalRelship]; DBStats.Stopping[DestroyRelship]; END; DestroyVariableFieldsOf: PROC[t: Relship, r: Relation] = BEGIN FOR al: LIST OF Attribute_ r.attributes, al.rest UNTIL al=NIL DO a: Attribute _ al.first; IF a.type = RopeType THEN QSetF[t, al.first, [null[]], FALSE] ELSE IF a.type = AnyDomainType OR IsDomainType[a.type] THEN QSetF[t, a, [null[]], FALSE ! DB.InternalError => TRUSTED {IF desperate THEN CONTINUE}]; ENDLOOP; END; SurrogateDestroyRelship: PROC[t: Relship] = BEGIN rel1: Relation; as: LIST OF Attribute; DBStats.Inc[SurrogateDestroyRelship]; WITH t SELECT FROM t1: TupleHandle => rel1 _ GetCachedRelshipInfo[SegmentOf[t1], t1]; t2: SurrogateRelshipHandle => rel1 _ t2.relation; ENDCASE => ERROR DB.Error[IllegalRelship]; as_ rel1.attributes.rest; -- skip the target attribute FOR as_ as, as.rest UNTIL as=NIL DO QSetF[t, as.first, MakeNullValueOfType[as.first.type]]; ENDLOOP; DestroyAllIndexEntries[t, rel1]; END; QEntityInfo: PUBLIC PROC [e: Entity] RETURNS [name: ROPE, domain: Domain] = BEGIN DBStats.Starting[EntityInfo]; IF IsSystem[e] THEN ERROR DB.Error[IllegalEntity]; [name, domain] _ GetCachedEntityInfo[SegmentOf[e], e]; DBStats.Stopping[EntityInfo]; RETURN[name, domain]; END; NameOf: PUBLIC PROC [e: Entity, domain: Domain] RETURNS [name: ROPE] = BEGIN IF IsSystem[e] THEN { IF e.name = NIL THEN ERROR InternalError ELSE RETURN[e.name] }; IF QDomainEq[domain, DomainDomain] OR QDomainEq[domain, RelationDomain] THEN -- name always second field of these: name_ PV2S[GetValFromHandle[e, tupleSetNameHandle, RopeType ]] ELSE IF QDomainEq[domain, AttributeDomain] THEN name_ PV2S[GetValFromHandle[e, attributeNameHandle, RopeType ]] ELSE name_ PV2S[GetValFromHandle[e, defaultNameHandle, RopeType ]]; RETURN[name]; END; ChangeName: PUBLIC PROC [e: Entity, s: ROPE] = BEGIN eD: Domain _ GetCachedEntityInfo[SegmentOf[e], e].domain; IF eD.isSystem THEN -- ChangeName is only write operation permitted on dictionary entities DictionaryChangeName[e, s] ELSE { old: Entity_ FetchEntity[eD, s]; IF old#NIL THEN IF EntityEq[old, e] THEN RETURN -- e already has this name ELSE ERROR DB.Error[NonUniqueEntityName]; ChangeNameBody[e, s, eD] }; END; ChangeNameBody: PROC [e: Entity, s: ROPE, eD: Domain, existingIndexEntry: BOOL _ TRUE] = BEGIN IF s=NIL THEN s_ ""; IF existingIndexEntry THEN DestroyEntityIndexEntries[e]; SetValFromHandle[e, defaultNameHandle, RopeType, , S2PV[s]]; CreateEntityIndexEntries[e]; END; DictionaryChangeName: PROC [e: Entity, s: ROPE] = BEGIN name: ROPE; eD: Domain; index: DBStorage.IndexHandle; [name, eD] _ GetCachedEntityInfo[SegmentOf[e], e]; IF QDomainEq[eD, AttributeDomain] THEN {SetValFromHandle[e, attributeNameHandle, RopeType, , S2PV[s]]; RETURN} ELSE IF QDomainEq[eD, DomainDomain] THEN index_ GetDomainIndex[eD.segment] ELSE IF QDomainEq[eD, RelationDomain] THEN index_ GetRelationIndex[eD.segment] ELSE ERROR DB.Error[IllegalEntity]; -- Don't know of any other system domains DBStorage.DeleteFromIndex[index, ConvertToUpper[name], e]; SetValFromHandle[e, tupleSetNameHandle, RopeType, , S2PV[s]]; DBStorage.InsertIntoIndex[index, ConvertToUpper[s], e]; END; QGetF: PUBLIC PROC[t: Relship, a: Attribute, string: BOOL_ FALSE] RETURNS[Value] = BEGIN ENABLE DB.InternalError => {IF desperate THEN GOTO GiveUp}; WITH t SELECT FROM t1: TupleHandle => { DBStats.Starting[GetF]; IF NullRelship[t1] THEN ERROR DB.Error[IllegalRelship]; IF QNullAttribute[a] THEN ERROR DB.Error[IllegalAttribute]; IF DBModelSchema.InvalidAttribute[a] THEN ERROR DB.Error[InvalidSchema]; { pv: PrivateValue _ GetValFromHandle[t1, a.fh, IF string THEN RopeType ELSE a.type, a.link]; DBStats.Stopping[GetF]; RETURN[PV2V[pv]] } }; t2: SurrogateRelshipHandle => { IF QNullAttribute[a] THEN ERROR DB.Error[IllegalAttribute]; IF DBModelSchema.InvalidAttribute[a] THEN ERROR DB.Error[InvalidSchema]; RETURN[SurrogateGetF[t2, a, string]] }; ENDCASE => ERROR InternalError; EXITS GiveUp => RETURN[[null[]]] END; QGetFS: PUBLIC PROC[t: Relship, a: Attribute] RETURNS [v: ROPE] = BEGIN temp: Entity; IF NullRelship[t] THEN ERROR DB.Error[IllegalRelship]; IF QNullAttribute[a] THEN ERROR DB.Error[IllegalAttribute]; IF DBModelSchema.InvalidAttribute[a] THEN ERROR DB.Error[InvalidSchema]; SELECT a.type FROM RopeType => RETURN[V2S[QGetF[t,a]]]; IntType => RETURN[IO.PutR[IO.int[V2I[QGetF[t, a]]]]]; TimeType => RETURN[IO.PutR[IO.time[V2T[QGetF[t, a]]]]]; BoolType => RETURN[IF V2B[QGetF[t, a]] THEN "TRUE" ELSE "FALSE"]; RecordType => ERROR DB.Error[NotImplemented]; ENDCASE => -- must be an entity IF a.link=Unlinked OR a.link=Remote THEN RETURN[V2S[QGetF[t, a, TRUE]]] ELSE IF NullEntity[temp_ V2E[QGetF[t, a]]] THEN v _ "" ELSE IF a.type#AnyDomainType THEN v _ GetCachedEntityInfo[a.relation.segment, temp].name ELSE { name: ROPE; domain: Domain; [name, domain] _ GetCachedEntityInfo[a.relation.segment, temp]; v _ Rope.Cat[domain.name, ": ", name] }; END; SurrogateGetF: PROC [t: SurrogateRelshipHandle, a: Attribute, string: BOOL] RETURNS [Value] = BEGIN rel1: Relation _ t.relation; IF QNullRelation[rel1] THEN ERROR DB.Error[IllegalAttribute]; IF DBModelSchema.InvalidRelation[rel1] THEN ERROR DB.Error[InvalidSchema]; DBStats.Starting[SurrogateGetF]; IF NOT QRelationEq[a.relation, rel1] THEN ERROR DB.Error[IllegalAttribute]; IF QAttributeEq[a, t.targetAttribute] THEN { v: Value _ DB.E2V[t.entity]; DBStats.Stopping[SurrogateGetF]; RETURN[v] } ELSE BEGIN { pv: PrivateValue _ GetValFromHandle[t.entity, a.fh, IF string THEN RopeType ELSE a.type, a.link ]; DBStats.Stopping[SurrogateGetF]; RETURN[PV2V[pv]] } END; END; QSetF: PUBLIC PROC[t: Relship, a: Attribute, v: Value, updateIndices: BOOL_ TRUE] = TRUSTED BEGIN ENABLE DB.InternalError => TRUSTED {IF desperate THEN CONTINUE}; WITH t SELECT FROM t1: SurrogateRelshipHandle => { IF QNullAttribute[a] THEN ERROR DB.Error[IllegalAttribute]; IF DBModelSchema.InvalidAttribute[a] THEN ERROR DB.Error[InvalidSchema]; SurrogateSetF[t1, a, [public[v]]]; RETURN }; t2: TupleHandle => { indexFactors: LIST OF IndexFactor _ NIL; indexList: LIST OF Index _ NIL; DBStats.Starting[SetF]; IF NullRelship[t2] THEN ERROR DB.Error[IllegalRelship]; IF QNullAttribute[a] THEN ERROR DB.Error[IllegalAttribute]; IF DBModelSchema.InvalidAttribute[a] THEN ERROR DB.Error[InvalidSchema]; IF IsDictionary[t2] THEN ERROR DB.Error[DictionaryUpdate]; WITH v: v SELECT FROM null => NULL; ENDCASE => IF (a.uniqueness=Key OR a.uniqueness=OptionalKey) AND NOT a.relation.is1to1 THEN BEGIN other: Relship_ FetchRelship[a.relation, LIST[[a, v]]]; IF other#NIL THEN ERROR DB.Error[NonUniqueKeyValue]; END; IF updateIndices THEN { indexFactors _ VL2TL[QGetPList[DBModelSchema.GetAttributeTuple[a], ifAttributeOf]]; FOR ifs: LIST OF IndexFactor _ indexFactors, ifs.rest UNTIL ifs = NIL DO indexList _ CONS[PV2E[SafeGetP[ifs.first, ifIndexIs]], indexList]; ENDLOOP }; IF updateIndices THEN DestroyIndexEntries[t, indexList]; SetValFromHandle[t2, a.fh, a.type, a.link, [public[v]]]; IF updateIndices THEN CreateIndexEntries[t, indexList]; DBStats.Stopping[SetF]; }; ENDCASE => ERROR InternalError; END; IsDictionary: PROC[t: TupleHandle] RETURNS[BOOL] = INLINE BEGIN IF t.tid SurrogateSetF[t1, a, v]; t2: TupleHandle => SetValFromHandle[t2, a.fh, a.type, a.link, v]; ENDCASE => ERROR InternalError; RETURN; END; QSetFS: PUBLIC PROC[t: Relship, a: Attribute, v: ROPE] = { QSetF[t, a, StringToValue[v, a]] }; SurrogateSetF: PROC [t: SurrogateRelshipHandle, a: Attribute, v: PrivateValue] = TRUSTED BEGIN DBStats.Starting[SurrogateSetF]; IF QAttributeEq[a, t.targetAttribute] THEN { v1: Value _ PV2V[v]; WITH v1 SELECT FROM null => IF t.entity#NIL THEN ERROR DB.Error[NotImplemented] -- Can't reset target if relation is surrogate ELSE t.entity_ V2E[v1]; entity => IF NOT CompatibleDomain[GetCachedEntityInfo[a.relation.segment, V2E[v1]].domain, a.type] THEN ERROR DB.Error[MismatchedAttributeValueType] ELSE { IF t.entity#NIL THEN ERROR DB.Error[NotImplemented] ELSE t.entity_ V2E[v1] } ENDCASE => ERROR DB.Error[MismatchedAttributeValueType] } ELSE -- normal case, setting Nth (N>1) attribute: aHandleProp gives handle in domain IF t.entity=NIL THEN ERROR DB.Error[MustSetKeyAttributeFirst] ELSE BEGIN indexFactors: LIST OF IndexFactor _ VL2TL[QGetPList[DBModelSchema.GetAttributeTuple[a], ifAttributeOf]]; indexList: LIST OF Index _ NIL; FOR ifs: LIST OF IndexFactor _ indexFactors, ifs.rest UNTIL ifs = NIL DO indexList _ CONS[PV2E[SafeGetP[ifs.first, ifIndexIs]], indexList]; ENDLOOP; DestroyIndexEntries[t, indexList]; SetValFromHandle [t.entity, a.fh, a.type, a.link, v ]; CreateIndexEntries[t, indexList]; END; DBStats.Stopping[SurrogateSetF]; END; EntityEq: PUBLIC SAFE PROC[e1, e2: Entity] RETURNS[BOOL] = TRUSTED BEGIN IF NullEntity[e1] THEN RETURN[NullEntity[e2]]; IF NullEntity[e2] THEN RETURN[FALSE]; IF SameSegment[e1, e2] OR IsSystem[e1] OR IsSystem[e2] THEN RETURN[e1.tid=e2.tid] ELSE RETURN[FALSE]; END; RelshipEq: PUBLIC SAFE PROC [r1, r2: Relship] RETURNS[BOOL] = { IF r1=NIL THEN RETURN[NullRelship[r2]]; IF r2=NIL THEN RETURN[NullRelship[r1]]; WITH r1 SELECT FROM r1t: TupleHandle => { WITH r2 SELECT FROM r2t: TupleHandle => RETURN[EntityEq[r1t, r2t]]; r2s: SurrogateRelshipHandle => RETURN[FALSE]; ENDCASE => ERROR }; r1s: SurrogateRelshipHandle => { WITH r2 SELECT FROM r2t: TupleHandle => RETURN[FALSE]; r2s: SurrogateRelshipHandle => RETURN[EntityEq[r1s.entity, r2s.entity]]; ENDCASE => ERROR }; ENDCASE => ERROR; }; NullEntity: PUBLIC SAFE PROC[e: Entity] RETURNS[BOOL] = CHECKED BEGIN RETURN[e=NIL OR e.tid=0] END; NullRelship: PUBLIC SAFE PROC [r: Relship] RETURNS[BOOL] = { IF r=NIL THEN RETURN[TRUE]; WITH r SELECT FROM r1: TupleHandle => RETURN[r1=NIL OR r1.tid=0]; r2: SurrogateRelshipHandle => RETURN[NullEntity[r2.entity]]; ENDCASE => ERROR; }; QDomainEq: PUBLIC PROC [d1, d2: Domain] RETURNS[BOOL] = { IF QNullDomain[d1] OR QNullDomain[d2] THEN RETURN[FALSE]; IF DBModelSchema.InvalidDomain[d1] OR DBModelSchema.InvalidDomain[d2] THEN ERROR DB.Error[InvalidSchema]; RETURN[Rope.Equal[d1.name, d2.name] AND d1.segment=d2.segment] }; QRelationEq: PUBLIC PROC [r1, r2: Relation] RETURNS[BOOL] = { IF QNullRelation[r1] OR QNullRelation[r2] THEN RETURN[FALSE]; IF DBModelSchema.InvalidRelation[r1] OR DBModelSchema.InvalidRelation[r2] THEN ERROR DB.Error[InvalidSchema]; RETURN[Rope.Equal[r1.name, r2.name] AND r1.segment=r2.segment] }; QAttributeEq: PUBLIC PROC [a1, a2: Attribute] RETURNS[BOOL] = { IF QNullAttribute[a1] OR QNullAttribute[a2] THEN RETURN[FALSE]; IF DBModelSchema.InvalidAttribute[a1] OR DBModelSchema.InvalidAttribute[a2] THEN ERROR DB.Error[InvalidSchema]; RETURN[Rope.Equal[a1.name, a2.name] AND QRelationEq[a1.relation, a2.relation]] }; QNullDomain: PUBLIC PROC [d: Domain] RETURNS[BOOLEAN] = { IF d=NIL OR (d.version=NIL AND ~d.isSystem) THEN RETURN[TRUE] ELSE RETURN[FALSE] }; QNullRelation: PUBLIC PROC [r: Relation] RETURNS[BOOLEAN] = { IF r=NIL OR (r.version=NIL AND ~r.isSystem) THEN RETURN[TRUE] ELSE RETURN[FALSE] }; QNullAttribute: PUBLIC PROC [a: Attribute] RETURNS[BOOLEAN] = { IF a=NIL OR (a.version=NIL AND ~a.isSystem) THEN RETURN[TRUE] ELSE RETURN[FALSE] }; QRelationOf: PUBLIC PROC [t: Relship] RETURNS [r: Relation] = BEGIN DBStats.Inc[RelationOf]; IF NullRelship[t] THEN ERROR DB.Error[IllegalRelship]; TRUSTED { WITH t SELECT FROM t1: TupleHandle => RETURN[GetCachedRelshipInfo[SegmentOf[t1], t1]]; t2: SurrogateRelshipHandle => RETURN[t2.relation]; ENDCASE => ERROR; }; END; GetValFromHandle: PUBLIC PROC [ t: TupleHandle, fh: DBStorage.FieldHandle, ft: DataType, fl: LinkType_ Linked] RETURNS [PrivateValue] = BEGIN SELECT ft FROM IntType => IF fh = aLinkIs.fh THEN RETURN[U2PV[GetTypeAndLink[t].link]] ELSE RETURN[I2PV[LOOPHOLE[DBStorage.Read2Word[t, fh]]]]; TimeType => { time: BasicTime.GMT; time_ BasicTime.FromPupTime[DBStorage.Read2Word[t, fh] ! BasicTime.OutOfRange => {time_ BasicTime.nullGMT; CONTINUE}]; RETURN[T2PV[time]]; }; RopeType => RETURN[S2PV[DBStorage.ReadVarByte[t, fh]]]; RecordType => { newfh: DBStorage.FieldHandle _ DBStorage.CreateFieldHandle[]; DBStorage.ReadNWord[t, fh, newfh]; RETURN[[handle[newfh]]] }; BoolType => RETURN[B2PV[LOOPHOLE[DBStorage.Read1Word[t, fh]]]]; ENDCASE => -- ft is a domain IF fl=Linked OR fl=Colocated THEN -- linked entity, stored as tuple ref RETURN[E2PV[DBStorage.ReadTID[t, fh]]] ELSE IF fl=Unlinked THEN -- entity name stored, look up the corresponding entity in ft RETURN[E2PV[QDeclareEntity[DataTypeToDomain[ft, SegmentOf[t]], PV2S[GetValFromHandle[t, fh, RopeType]]]]] ELSE -- entity is remote, look up entity in another segment RETURN[E2PV[IDToEntity[PV2S[GetValFromHandle[t, fh, RopeType]]]]]; END; SetValFromHandle: PUBLIC PROC [ t: TupleHandle, fh: DBStorage.FieldHandle, ft: DataType, fl: LinkType_ Linked, v: PrivateValue] = TRUSTED BEGIN CheckNullified[t]; WITH v1: v SELECT FROM public => WITH v2: v1.val SELECT FROM integer => IF ft#IntType THEN ERROR DB.Error[MismatchedAttributeValueType] ELSE DBStorage.Write2Word[t, fh, LOOPHOLE[v2.value]]; time => IF ft#TimeType THEN ERROR DB.Error[MismatchedAttributeValueType] ELSE DBStorage.Write2Word[t, fh, BasicTime.ToPupTime[v2.value]]; rope => IF ft#RopeType THEN ERROR DB.Error[MismatchedAttributeValueType] ELSE DBStorage.WriteVarByte[t, fh, v2.value]; boolean => IF ft#BoolType THEN ERROR DB.Error[MismatchedAttributeValueType] ELSE DBStorage.Write1Word[t, fh, LOOPHOLE[v2.value] ]; entity => { IF SameSegment[v2.value, t] THEN {IF NOT CompatibleDomain[GetCachedEntityInfo[SegmentOf[v2.value], v2.value].domain, ft] THEN ERROR DB.Error[MismatchedAttributeValueType]} ELSE IF fl#Remote THEN ERROR DB.Error[MismatchedSegment]; IF fl=Linked OR fl=Colocated THEN -- linked: store the entity reference in a group BEGIN temp: DBStorage.GroupScanHandle; DBStorage.WriteTID[t, (temp_ DBStorage.OpenScanGroup[v2.value, fh, Last])]; DBStorage.CloseScanGroup[temp] END ELSE IF fl=Unlinked THEN -- not linked: store name as string SetValFromHandle[t, fh, RopeType, , S2PV[GetCachedEntityInfo[SegmentOf[v2.value], v2.value].name]] ELSE -- fl=Remote: store segment: domain: name SetValFromHandle[t, fh, RopeType, , S2PV[EntityToID[v2.value]]]; }; null => IF ft=RopeType THEN DBStorage.WriteVarByte[t, fh, NIL] ELSE IF IsDomainType[ft] THEN IF fl=Linked OR fl=Colocated THEN DBStorage.WriteTIDNil[t, fh] ELSE DBStorage.WriteVarByte[t, fh, NIL] ELSE -- give the guy another chance, construct a null value for him ... SetValFromHandle[t, fh, ft, fl, [public[MakeNullValueOfType[ft]]]]; ENDCASE => DB.Error[IllegalValue]; handle => IF ft#RecordType THEN ERROR DB.Error[IllegalValue] ELSE DBStorage.WriteNWord[t, fh, v1.val]; ENDCASE => DB.Error[IllegalValue]; END; CompatibleDomain: PROC [d: Domain, dt: DataType] RETURNS [BOOL] = { IF QNullDomain[d] THEN ERROR DB.Error[IllegalDomain]; IF DBModelSchema.InvalidDomain[d] THEN ERROR DB.Error[InvalidSchema]; IF dt = AnyDomainType THEN RETURN[TRUE]; IF d.isSystem THEN SELECT d.name FROM "Domain" => RETURN[dt = $DomainDomain OR dt = $DataTypeDomain]; "Relation" => RETURN[dt = $RelationDomain]; "Attribute" => RETURN[dt = $AttributeDomain]; "DataType" => RETURN[dt = $DataTypeDomain]; "Index" => RETURN[dt = $IndexDomain]; "IndexFactorDomain" => RETURN[dt = $IndexFactorDomain]; ENDCASE => ERROR DB.Error[IllegalDomain]; IF Atom.MakeAtom[d.name] = dt THEN RETURN[TRUE]; { d1: Domain _ QSuperType[d]; IF NOT QNullDomain[d1] THEN RETURN[CompatibleDomain[d1, dt]] ELSE RETURN[FALSE] } }; GetTypeEntity: PROC [t: TupleHandle] RETURNS [typeEntity: Entity] = BEGIN a: Attribute; IF IsSystem[t] THEN ERROR InternalError; a _ DBModelSchema.TupleToAttribute[t]; RETURN[DataTypeToEntity[a.type, a.relation.segment]]; END; CheckNullified: PROC [p: TupleHandle] = INLINE { IF p=NIL THEN ERROR DB.Error[NILArgument] ELSE IF p.tid=NoTID THEN ERROR DB.Error[NullifiedArgument] }; IDToEntity: PROC[name: ROPE] RETURNS[e: Entity] = { segNameLength: INT = Rope.Find[ name, ":" ]; domainNameLength: INT = Rope.Find[ name, ":", segNameLength+2 ] - segNameLength - 2; segName: ROPE = Rope.Substr[ name, 0, segNameLength ]; domainName: ROPE = Rope.Substr[ name, segNameLength+2, domainNameLength ]; entityName: ROPE = Rope.Substr[ name, segNameLength+domainNameLength+4 ]; segment: Segment = Atom.MakeAtom[ segName ]; domain: Domain; domain _ DBModel.QDeclareDomain[ domainName, segment, OldOnly ! DB.Error => TRUSTED {domain _ NIL; CONTINUE} ]; e _ IF QNullDomain[domain] THEN NIL ELSE FetchEntity[ domain, entityName ] }; EntityToID: PROC[e: Entity] RETURNS[name: ROPE] = { ename: ROPE; domain: Domain; segment: SegmentHandle _ SegmentOf[e]; [ename, domain] _ GetCachedEntityInfo[segment, e]; RETURN[Rope.Cat[Atom.GetPName[segment.segment], ": ", domain.name, ": ", ename]] }; SameSegment: PROC [x, y: DBStorage.TupleHandle] RETURNS [BOOL] = INLINE {RETURN[DBStorage.SegmentIDFromTuple[x]=DBStorage.SegmentIDFromTuple[y]]}; SetMultipleF: PROC[t: Relship, alh: PrivateAttributeValueList] = BEGIN FOR alhT: PrivateAttributeValueList _ alh, alhT.rest UNTIL alhT=NIL DO QSetF[t, alhT.first.attribute, PV2V[alhT.first.lo], FALSE]; ENDLOOP END; PV2V: PROC[v: PrivateValue] RETURNS[Value] = TRUSTED INLINE { RETURN[WITH v: v SELECT FROM public => v.val, ENDCASE => ERROR DB.Error[MismatchedValueType] ] }; END. @File: DBModelBasicImpl.mesa Copyright c 1985 by Xerox Corporation. All rights reserved. Contents: Implementation of basic DBView operations on entities and relships. Last edited by: Rick Cattell on: December 12, 1983 2:26 pm Eric Bier on: July 16, 1982 2:38 pm Willie-Sue on February 20, 1985 10:36:12 am PST Donahue, September 5, 1985 5:59:14 pm PDT Widom, September 13, 1985 4:54:38 pm PDT Table of contents 1. Data types and globals 2. Creating and destroying entities and relationships 3. Getting and setting names and attributes 4. Miscellaneous exported procedures 5. Miscellaneous support routines Types 1. Creating and destroying entities and relationships Checks to see if the entity already exists. If so, it returns it, if not, it returns NIL. If more than one entity satisfies the constraints, it signals MultipleMatch. Uses segment arg only for dictionary entity lookup, to know which segment to search. Checks explicitly for lookup of the system entities. Creates a new entity in d. The domain d must be a client-defined (dictionary). Enters the entity in d's name index. Converts a string to a value of the type required by a, doing the appropriate string conversion or a Fetch if type is a Domain. Must search every domain in the system unless s contains domain name or is empty we reach here if NO domain contained an entity named e. Destroys a data tuple, by: (1) deleting all references to it in indexes and groups, (2) calling the storage level to destroy the tuple itself, its TH, and invalidate all THs containing the same tid. Note the semantics when version=NewOnly slightly different than FetchRelship, we want to create a new relship no matter what in this case while FetchRelship checks to make sure it's the only one with these attribute values. Creates a new tuple in r; r must be relation entity. Enters the new relationship in any appropriate indexes. r must be a system or dictionary tupleset. Implemented by calling RelationSubset and checking for exactly one match. Raises MultipleMatch if more than one relship returned by RelationSubset. Returns NIL if no tuples match. Note that unlike ordinary CreateRelship, we do not create any index entries; creating a surrogate Relship just creates a surrogate referring to the actual entries, the index entries are created or destroyed with the entity in which the data reside. Note that t.vEntity is not yet known! Destroys a (non-schema) relationship, by: (1) destroying any index entries referencing this relationship (2) removing it from any DBStorage groups by NILing out entity-valued fields, (3) NILing out string-valued fields to get rid of any dangling strings created by DBStorage (4) invalidating tupleset cache and calling the storage level to destroy the TupleHandle, Invalidates all TupleHandles containing the same tid as a side effect of (4). Constraints: Must destroy index entries (1) before NILing out entity refs and strings because they are used to figure out which index entries to destroy. Steps (2) and (3) bypass the normal creating/destroying index entries on attribute update, for efficiency. NIL out variable-length string attributes so that DBStorage deletes "string tuples" associated with t for strings that overflow the length specified for the attribute. Does NOT create or destroy any index entries associated with the attribute, the caller must do that. Also destroys backlinks to t (in groups) by storing NIL in those attributes (a) of t that point to entities (the Storage level removes the corresponding backlinks). This NILs out field whether it was link field or string field (want to either way). We disable update of index entries for efficiency, we'll destroy them later. Puts an appropriate null value in all attributes but the first (which refs the entity in which the others are actually stored). null out all other attributes of t's relship 2. Getting and setting names and attributes Works on system, dictionary, and data entities. Check that name is unique for this domain unless null, then set it and make an entry in the name index for this domain. On system entities, must use special name handles according to position of name field Set name of domain or relation and re-enter in the index Gets attribute a of t by calling the appropriate DBStorage routine. We handle surrogate tuples as special case. GetAttributeInfo and GetValFromHandle deal correctly with the other two cases: (1) t is dictionary tuple, a is system tuple and (2) t is data tuple, a is dictionary tuple. If string is TRUE, attribute is fetched as string regardless of actual type; this is used to over-ride translation of string to entity on remote entity types. We can fetch name string directly, don't need to GetF the entity itself Fetch the entity, return null if null, prepend domain if AnyDomainType Retrieves field of surrogate tuple the silly case: retrieving back the entity the relship corresponds to normal case: must retrieve attribute of target domain instead Call appropriate DBStorage routine according to field type. "t" must be a data or surrogate tuple, "a" must be a dictionary tuple. check for uniqueness Exported to DBModelPrivate; for setting ordinary or surrogate dictionary tuple attributes. Attempts to convert v to attribute of type required for attribute a of t. Signals MismatchedAttributeValue if can't. Set field of a surrogate tuple. This may be a dictionary tuple, although only if called through SafeSetF since QSetF (called by client) will give error in that case. resetting t's entity; the semantics of this are tricky 3. Miscellaneous exported procedures If e1 or e2 are NIL or nullified, returns TRUE iff the other is, too. Else if e1 and e2 are in the same segment, returns TRUE iff e1 and e2 ref same entity. Else returns FALSE. 4. Miscellaneous support routines All fetch operations go through this lowest level procedure. Its arguments (which give the handle, type, and link bool) should have been fetched by a call to GetAttributeInfo. We make a special case here for the simulated system attributes aTypeIs and aLinkIs (See explanation in DBModelSystemImpl of these attributes). WARNING: if we ever fetch any RecordType fields other than field handles, the following won't do! We'll have to pass down the REF for ReadNWord. All store operations go through this lowest level procedure. Its arguments (which give the handle, type, and link bool) should have been fetched by a call to GetAttributeInfo. The aTypeIs and aLinkIs attributes must be set using SetTypeAndLink, they are illegal here. We check that the type of v corresponds to ft of attribute at this level, and in the case of entity-valued attributes, check that v is of the right type with CompatibleDomain. If fl=Unlinked, then we store the name of the entity rather than the entity itself. If fl=Remote, we store the "segment: domain: name" as string instead of entity. fl=Colocated not yet implemented, treated just like fl=Linked. LOOPHOLE because Write1Word expects a CARDINAL. Type of v is OK (except we can't check this for cross-segment refs) TID backpointer at end of group for the moment Returns TRUE iff elements of datatype dt are compatible with domain d Otherwise see if dt is compatible with d's SuperType Use for Entities and Relships Copied from Donahue's DBNamesImpl.mesa. Does NOT destroy or create any existing index entries, since used only by CreateRelship and SurrogateCreateRelship. For same reason, no type checking necessary on t. Κ"Ο˜šœ™Jšœ Οmœ1™<—JšœM™Mšœ™Jšœ*™*Jšœ#™#Jšœ/™/Icode™)J™(—J˜Jšœ™Jšœ™Jšœ5™5Jšœ+™+Jšœ$™$Jšœ!™!J˜J˜šΟk ˜ Jšœžœ˜ J˜ Jšœ ˜ Jšœžœ˜(J˜ J˜Jšžœ˜J˜J˜J˜Jšžœ˜J˜J˜—šœžœž˜šžœ&žœ˜AJšœžœ˜'—šžœžœΟc˜Jšœ Ÿ˜JšœŸ*˜;J˜——Jšžœžœžœ,˜9˜Jšœžœžœ˜—head1šœ™Jšžœžœžœ˜—šœ5™5š Οnœžœžœžœžœ˜TJšžœ˜Jšž˜š œžœ žœ˜-JšžœŸ&˜,Jš žœ žœžœžœžœ˜DJšœžœžœžœ˜=Jšœžœžœžœ˜&J˜Jšžœ˜—Jšžœžœžœ˜5Jšžœ žœžœ˜EJšžœ žœžœ˜1šžœžœžœŸ:˜NJš žœžœžœžœžœžœ˜A—J˜šžœžœž˜Jšžœžœžœž˜:—šžœŸ˜ Jš žœžœžœžœžœžœ˜B—Jšžœ˜J˜—š  œžœžœžœ˜>JšœY™YJšœL™LJšœT™Tšœ4™4Jšž˜J˜Jšœžœ˜Jšžœžœžœ6˜Ršžœžœ˜#šžœžœž˜Jšœžœžœ˜:Jšœžœžœ˜>Jšœžœžœ˜@Jšœžœžœ˜>Jšœžœžœ˜8Jšœžœžœ˜DJšžœžœ˜——J˜J˜/J˜J˜J˜J˜Jšžœžœžœžœ˜"Jšžœžœžœžœ˜Jšœžœžœžœ˜U——Jšžœ˜J˜—š  œžœžœžœ ˜;JšœM™MJšœ1™1Jšžœžœžœ˜šžœž˜˜ Jšžœ ˜—˜ Jš žœžœžœ žœžœ˜I—˜ Jš žœžœžœžœžœ˜6Jšžœžœ˜#—˜ Jšžœžœžœžœ˜,Jš žœžœžœžœžœ˜3Jšžœžœ(˜2—˜JšœP™PJšžœžœžœ ˜%šžœžœž˜"JšžœŸ)˜/JšœT˜TJšžœžœžœžœ˜2Jšžœ,˜2Jšž˜—šžœŸ˜$Jšž˜Jšœ˜Jšœ˜Jšœ˜JšœQ˜Qšžœ!ž˜(˜"Jšžœžœžœ0žœ˜QJšžœ˜——Jšœ7™7Jšœ,˜,Jšžœ˜Jšžœ˜——Jšžœžœ'˜7—Jšžœ˜J˜—š   œžœžœžœžœ˜8Jšž˜Jš œžœ žœžœžœ žœ)˜VJšžœ˜J˜—š   œžœžœžœžœ˜+Jšžœžœžœ žœ(˜FJšœžœžœžœ˜Jšžœ˜J˜—š œžœžœžœ˜,Jšžœž˜ Jš žœžœžœžœŸ˜4šžœ˜ Jšœ,˜,Jšžœ˜—Jšžœ˜J˜—š œžœžœ ˜(Jšœ™Jšœ8™8JšœB™BJšœ/™/Jšž˜J˜ Jšžœžœžœ˜šžœ ž˜Jšžœ˜ —šž˜Jšž˜JšœŸ:˜MJšœŸ%˜CJšœ?Ÿ˜\J˜Jšžœ˜—J˜ Jšžœ˜J˜—š œžœžœ˜Jšœ'žœžœ˜]JšœM™MJšœN™NJšœB™BJšž˜J˜'Jšžœžœžœ˜9Jšžœ"žœžœ˜GJ˜ Jš žœžœžœžœžœ ˜JJ˜Jš žœžœžœžœžœ ˜IJšžœ˜J˜—š  œžœžœ˜SJšž˜š žœ žœžœžœžœ˜!šžœžœžœ˜Jšœr˜r——Jšžœ˜—J˜š   œžœžœ/žœžœ˜cJšœQ™QJšœ™Jšž˜J˜Jšžœ žœžœ˜4Jšžœ žœžœ"˜9J˜<šžœžœžœ˜%Jš œžœ žœžœžœžœŸœ˜j—J˜Jšžœ˜J˜—š  œžœ'žœ˜NJšœQ™QJšœJ™JJšœD™DJšž˜J˜Jšœžœ˜J˜J˜'J˜J˜J˜J˜Jš žœ žœžœžœŸ˜JJšžœ˜ Jšžœ˜J˜—š œžœžœ/žœ˜sJšœU™UJšœW™WJšœJ™JJšžœ˜ J˜$Jšœžœ ˜&J˜J˜(Jšœ%™%Jšžœžœžœ˜'Jšžœ˜ Jšžœ˜J˜—š œžœžœ˜*Jšœ)™)Jšœ>™>JšœM™MJšœ[™[JšœY™YJšœM™MJšœV™VJšœV™VJšœX™XJšž˜J˜!šžœžœž˜˜Jšœ ˜ Jšžœžœ%žœ˜EJšœ,˜,Jšžœ žœžœ˜4šžœ žœ˜Jšœ˜J˜!Jšžœ˜ —JšœŸ˜%Jšœ Ÿ˜&JšœŸ˜$—Jšœ:˜:Jšžœžœ˜*—J˜!Jšžœ˜J˜—š œžœ˜9Jšœ^™^JšœY™YJšœͺ™ͺJšœO™OJšž˜š žœžœžœ"žœžœž˜@J˜Jšžœžœžœž˜Bšžœ˜Jšžœž˜JšœS™SJšœL™LJš œžœžœžœžœ žœžœ˜X—Jšžœ˜—Jšžœ˜J˜—š œžœ˜+JšœX™XJšœ&™&Jšž˜Jšœ˜Jšœžœžœ ˜J˜%šžœžœž˜JšœB˜BJšœ1˜1Jšžœžœ˜*—JšœŸ˜6Jšœ,™,šžœžœžœž˜#Jšœ7˜7Jšžœ˜—J˜ Jšžœ˜——šœ+™+š   œžœžœ žœžœ˜KJšž˜J˜Jšžœ žœžœ˜2J˜6J˜Jšžœ˜šžœ˜J˜——š  œžœžœžœžœ˜FJšœ/™/šž˜šžœ žœ˜Jš žœ žœžœžœžœžœ ˜?—šžœ!žœ#žœŸ%˜rJšœ>˜>šžœžœ$ž˜/Jšœ?˜?—šž˜Jšœ>˜>——Jšžœ˜ Jšžœ˜J˜——š  œžœžœžœ˜.Jšž˜Jšœ9˜9šžœ žœŸF˜ZJ˜—šžœ˜Jšœ ˜ Jšžœžœž˜JšœžœžœžœŸ˜™>Jšžœž˜ J˜Jšžœžœž˜šœ žœ žœž˜%šœ ˜ Jšžœ žœžœ'˜?Jšžœžœ ˜5—šœ˜Jšžœ žœžœ'˜@Jšžœ<˜@—šœ˜Jšžœ žœžœ'˜@Jšžœ)˜-—šœ ˜ Jšžœ žœžœ'˜@šžœžœ ˜6Jšœ/™/——˜ šžœž˜ šœžœžœQž˜\Jšžœ(˜-——Jšžœžœ žœžœ˜9JšœC™Cšžœ žœžœŸ0˜RJšž˜J˜ ˜KJšœ.™.—J˜Jšž˜—šžœžœ žœŸ#˜Jšžœžœ˜'—šžœŸB˜GJ˜C———Jšžœ˜"—šœ žœžœžœ˜