Segment operations
QDeclareSegment:
PUBLIC
PROC[
filePath: ROPE, segment: Segment, number: SegmentIndex ← 0, readonly: BOOL← FALSE,
createIfNotFound: BOOL← TRUE, nPagesInitial, nPagesPerExtent: NAT ] = {
Open a segment. Simply registers it, doesn't open the file till transaction opened.
Note that we have a slightly different semantics for the version than DBStorage.
We NEVER clobber an existing segment, that must be done with EraseSegment.
fileVersion: DBStorage.VersionOptions← IF createIfNotFound THEN None ELSE OldFileOnly;
IF readonly AND createIfNotFound THEN ERROR Error[WriteNotAllowed];
IF NOT initialized THEN ERROR Error[DatabaseNotInitialized];
IF segment=NIL THEN segment← Atom.MakeAtom[ParseSegmentName[filePath].name];
IF number=0 THEN number← MapSegmentToNumber[segment];
DBStorage.AttachSegment[
filePath, segment, number, readonly, fileVersion, FALSE, nPagesInitial, nPagesPerExtent];
};
QEraseSegment:
PUBLIC
PROC[segment: Segment, useTrans: Transaction] = {
Erases a segment. Closes transaction if it was open. Leaves transaction open (committed).
filePath: ROPE; number: SegmentIndex; readOnly: BOOL; trans: Transaction;
nPagesInitial, nPagesPerExtent: INT;
[filePath, number, trans, readOnly, nPagesInitial, nPagesPerExtent]←
DBStorage.GetSegmentInfo[segment];
QCloseTransaction[trans]; -- does nothing if no transaction open
DBStorage.AttachSegment[
filePath, segment, number, readOnly, None, TRUE, nPagesInitial, nPagesPerExtent];
QOpenTransaction[segment: segment, useTrans: useTrans];
QMarkTransaction[DBStorage.GetSegmentInfo[segment].trans]; -- useTrans might be NIL
};
QGetSegmentInfo:
PUBLIC
PROC[segment: Segment]
RETURNS [
filePath: ROPE, number: SegmentIndex, trans: Transaction, readOnly: BOOL] = {
CheckNIL[segment];
[filePath, number, trans, readOnly, , ] ← DBStorage.GetSegmentInfo[segment];
IF filePath = NIL THEN number ← MapSegmentToNumber[segment ! Error => CONTINUE];
RETURN[filePath, number, trans, readOnly]
};
QGetSegments:
PUBLIC
PROC
RETURNS [sl:
LIST
OF Segment] = {
addToList:
PROC [s: Segment, segmentIndex: DBStorage.SegmentIndex]
RETURNS [stop:
BOOL] =
{sl← CONS[s, sl]; RETURN[FALSE]};
sl← NIL;
DBStorage.EnumerateSegments[addToList];
RETURN[sl];
};
QGetBuiltinSegments:
PUBLIC
PROC RETURNS[
LIST
OF Segment] = {
must construct an entirely new list of new records, so a user cannot change the numbering; hence the very awful code that follows
sai: LIST OF Segment ← CONS[$Squirrel, NIL]; -- known start
last: LIST OF Segment ← sai;
FOR sL:
LIST
OF SegmentAndIndex ← mapSegmentToNumberList.rest, sL.rest
UNTIL sL=
NIL
DO
last.rest ← CONS[sL.first.segment, NIL];
last ← last.rest;
ENDLOOP;
RETURN[sai];
};
MapSegmentToNumber:
PROC [segment: Segment]
RETURNS [
NAT] = {
FOR mL:
LIST
OF SegmentAndIndex ← mapSegmentToNumberList, mL.rest
UNTIL mL=
NIL
DO
IF segment = mL.first.segment THEN RETURN[mL.first.index];
ENDLOOP;
ERROR Error[CannotDefaultSegment]; -- seg number not given & can't guess
};
SegmentAndIndex: TYPE = RECORD[segment: Segment, index: SegmentIndex];
mapSegmentToNumberList:
LIST
OF SegmentAndIndex =
LIST[ [$Squirrel, 100B],
[$Foo, 101B],
[$Icons, 140B],
[$Walnut, 200B],
[$Hickory, 210B],
[$Grapenut, 220B],
[$Coconut, 230B],
[$Help, 240B],
[$Nuthatch, 250B],
[$Finger, 260B],
[$Test, 300B],
[$Whiteboard, 310B],
[$Tool, 320B],
[$WalnutSortDef, 330B], -- reserve [330B .. 350B) for Pasadena
[$Chestnut, 400B] ];
Schema definition operations
QDeclareDomain:
PUBLIC
PROC [
name: ROPE, segment: Segment,
version: Version← NewOrOld, estRelships: INT← 5] RETURNS [d: Domain] = {
Creates a domain of this name or fetches it if it already exists.
IF NOT initialized THEN ERROR Error[DatabaseNotInitialized];
IF name=NIL THEN name← "";
IF name.Length[]#0
THEN
BEGIN
d← QFetchEntity[DomainDomain, name, segment];
IF d#NIL THEN IF version=NewOnly THEN ERROR Error[AlreadyExists] ELSE RETURN[d];
IF version=OldOnly THEN RETURN[NIL];
END;
Create a new domain. Must set name and index entry manually 'since SetP assumes exists.
d← DBStorage.CreateSystemPageTuple[T2ST[DomainDomain].vTuple, NIL, segment];
DBStorage.CreateTupleset[d, Basics.LowHalf[estRelships]];
SetValFromHandle[d, tupleSetNameHandle, RopeType, Unlinked, name];
DBStorage.InsertIntoIndex[GetDomainIndex[segment], ConvertToUpper[name], d];
create the name field and index for the entities of this domain
[]← DBStorage.AddField[d, MakeFD[RopeType, DefaultNameLength]];
[]← SafeSetP[d, dIndexProp, CreateTupleSetIndex[d]];
DBModelPrivate.FlushCaches[];
};
QDeclareRelation:
PUBLIC
PROC[
name: ROPE, segment: Segment, version: Version← NewOrOld]
RETURNS [r: Relation] = {
Creates a relation of this name or fetches it if it already exists.
IF NOT initialized THEN ERROR Error[DatabaseNotInitialized];
IF name=NIL THEN name← "";
IF name.Length[]#0
THEN
BEGIN
r← QFetchEntity[RelationDomain, name, segment];
IF r#NIL THEN IF version=NewOnly THEN ERROR Error[AlreadyExists] ELSE RETURN[r];
IF version=OldOnly THEN RETURN[NIL];
END;
Create a new relation. Must set name and index entry manually 'since SetP assumes exists.
r← DBStorage.CreateSystemPageTuple[T2ST[RelationDomain].vTuple, NIL, segment];
DBStorage.CreateTupleset[r, 0 -- NO refs to Relships --];
[]← SafeSetP[r, r1to1Prop, B2V[FALSE]];
SetValFromHandle[r, tupleSetNameHandle, RopeType, Unlinked, name];
DBStorage.InsertIntoIndex[GetRelationIndex[segment], ConvertToUpper[name], r];
DBModelPrivate.FlushCaches[];
};
QDeclareAttribute:
PUBLIC
PROC [
r: Relation, name: ROPE, type: DataType← NIL, uniqueness: Uniqueness ← None,
length: INT← 0, link: LinkType← Linked, version: Version← NewOrOld]
RETURNS[a: Attribute] = {
Create a new attribute dictionary entity, and define a new fieldhandle for
the appropriate tupleset.
NameEqual:
PROC [e: Entity]
RETURNS[
BOOL] =
{RETURN[name.Equal[QNameOf[e], FALSE]]};
fh: DBStorage.FieldHandle;
attCount: CARDINAL;
IF r=NIL THEN RETURN[NIL];
CheckRelation[r];
IF link=Colocated THEN ERROR Error[NotImplemented];
a← SearchEntityList[VL2EL[QGetPList[r, aRelationOf]], NameEqual];
IF a#
NIL
THEN
IF version=NewOnly
THEN
ERROR Error[AlreadyExists]
ELSE
BEGIN -- Check that existing attribute of proper type.
IF type#
NIL
AND
NOT Eq[V2E[QGetP[a, aTypeIs]], type]
THEN
ERROR Error[MismatchedExistingAttribute];
RETURN[a]
END;
No existing attribute, so create it.
IF version = OldOnly THEN RETURN[NIL];
IF type=NIL THEN ERROR Error[NILArgument]; -- Must not default type, if new!
a← DBStorage.CreateSystemPageTuple[T2ST[AttributeDomain].vTuple, r];
QChangeName[a, name];
DBModelPrivate.SetTypeAndLink[a, type, link];
[]← SafeSetP[a, aRelationIs, r];
[]← SafeSetP[a, aLengthIs, I2V[length]];
[]← SafeSetP[a, aUniquenessIs, U2V[uniqueness] ];
Create the fields for values of this attribute in a's Relation or ref'd Domain:
attCount← NumberOfAttributes[r];
The surrogate relation optimization follows. If:
(1) this is the first attribute of r
(2) it is a single-attribute primary key
(3) it is an entity-valued attribute but not of type AnyDomainType
(4) the domain it references is empty (has not entities yet)
(5) the domain it refs has no subdomains (else they'd need the attr too)
then we set r1to1Prop to TRUE for this relation, and store the 2nd-Nth
attributes with the domain instead; the relation's relships will be
surrogates not actually stored as tuples
IF attCount=1
AND uniqueness=Key
AND surrogatesEnabled
THEN
SELECT type
FROM
AnyDomainType, RopeType, BoolType, IntType =>
NULL;
ENDCASE =>
IF
NOT Eq[QDomainOf[type], DomainDomain]
THEN
ERROR Error[IllegalValueType]
user specified type that was not domain or built-in type
ELSE
IF EmptyDomain[type]
AND GetCachedDomainInfo[type].subDomains=
NIL
THEN
BEGIN -- this is it! we can do the surrogate relation optimization!
[]← SafeSetP[r, r1to1Prop, B2V[TRUE]];
[]← SafeSetP[a, aDomainIs, type];
RETURN
END;
IF V2B[SafeGetP[r, r1to1Prop]]
THEN
we are defining Nth attribute of a 1 to 1 (surrogate) relation where N>1:
BEGIN
storageDomain: Domain← V2E[SafeGetP[GetFirstAttribute[r], aTypeIs]];
fh← DBStorage.AddField[storageDomain, MakeFD[type, length, link=Linked, a]];
[]← SafeSetP[a, aHandleProp, fh];
END
ELSE
-- we are defining an attribute of a normal relation:
BEGIN
fh← DBStorage.AddField[r, MakeFD[type, length, link=Linked, a]];
[]← SafeSetP[a, aHandleProp, fh];
END;
DBModelPrivate.FlushCaches[];
};
QDeclareSubType:
PUBLIC
PROC [of, is: Domain] = {
Check that super (of) is not already sub (is) of sub, that super has no entities in it,
and that super is not already supertype of sub (just return in this case).
st: Relship;
rs: RelshipSet← QRelationSubset[dSubType, LIST[ [dSubTypeIs, is], [dSubTypeOf, of] ] ];
theseBetterNotIncludeSuper: LIST OF Domain← GetCachedDomainInfo[is].subDomains;
segment: Segment← QSegmentOf[of];
FOR dlT:
LIST
OF Domain← theseBetterNotIncludeSuper, dlT.rest
UNTIL dlT=
NIL
DO
IF Eq[dlT.first, of]
THEN
ERROR Error[IllegalSuperType] -- super is already of a subdomain!
ENDLOOP;
IF QNextRelship[rs]#
NIL
THEN
It's already a subType, just return and leave it that way
{QReleaseRelshipSet[rs]; RETURN};
QReleaseRelshipSet[rs];
IF
NOT EmptyDomain[of]
THEN
Currently can't define subdomain if super already has entries
ERROR Error[NotImplemented]; -- But client can resume if knows no surrogates
The following line sends the compiler into an address fault if don't use T2CT!
st← DBStorage.CreateSystemPageTuple[T2ST[T2CT[dSubType]].vTuple, NIL, segment];
SafeSetF[st, dSubTypeIs, is];
SafeSetF[st, dSubTypeOf, of];
};
QDeclareIndex:
PUBLIC
PROC[
r: Relation, order: AttributeList, version: Version] RETURNS[i: Index] = {
Creates an index on relation r, keyed on the attributes in order; r may not have tuples.
We enter the index factors such that a group scan on the index will return them in order
of increasing lexicographic significance. We check to see if there's another identical
index on r. We allow an index on all kinds of attributes, including entity-valued ones.
On entity-valued attributes, the string values used will actually be the entity names.
count: INT;
if: IndexFactor;
ifs: LIST OF IndexFactor;
See if the index (ie an index with the same attribute order) already exists.
Try all the indexes that include the first attribute in order:
ifs← VL2EL[QGetPList[order.first, ifAttributeOf]];
FOR ifsT:
LIST
OF IndexFactor← ifs, ifsT.rest
UNTIL ifsT=
NIL
DO
i← V2E[QGetP[ifsT.first, ifIndexIs]];
IF SameIndex[i, order]
THEN
IF version=NewOnly THEN ERROR Error[AlreadyExists] ELSE RETURN[i];
REPEAT
FINISHED => IF version=OldOnly THEN RETURN[NIL];
ENDLOOP;
Check that relation empty, then create index and index factors.
IF NOT EmptyRelation[r] THEN ERROR Error[NotImplemented];
i← CreateTupleSetIndex[r]; count← 0;
FOR orderL: AttributeList← order, orderL.rest
UNTIL orderL=
NIL
DO
if← DBStorage.CreateSystemPageTuple[T2STT[IndexFactorDomain].vTuple, r];
[]← SafeSetP[if, ifIndexIs, i]; -- Each factor points to its index...
[]← SafeSetP[if, ifOrdinalPositionIs, NEW[INT← count]]; -- ... gives its pos'n in the order
[]← SafeSetP[if, ifAttributeIs, orderL.first]; -- .. and the attribute it refers to
count← count+1;
ENDLOOP;
DBModelPrivate.FlushCaches[];
};
CreateTupleSetIndex:
PUBLIC
PROC[ts: TupleSet]
RETURNS [i: Index] = {
Creates the storage level index on a domain or relation, and the Index entity
i← DBStorage.CreateSystemPageTuple[T2STT[IndexDomain].vTuple, ts];
DBStorage.CreateIndex[i]; -- storage level sets iHandleProp
};
QDeclareProperty:
PUBLIC
PROC [
relationName: ROPE, of: Domain, is: DataType, segment: Segment,
uniqueness: Uniqueness← None, version: Version← NewOrOld] RETURNS [aIs: Attribute] = {
r: Relation← QDeclareRelation[relationName, segment, version];
rOf: Attribute← QDeclareAttribute[r, "of", of, Key, 0, Linked, version];
rIs: Attribute← QDeclareAttribute[r, "is", is, uniqueness, 0, Linked, version];
RETURN[rIs];
};
QDestroyDomain:
PUBLIC
PROC [d: Domain] = {
Destroys a domain, any connections it has to super-domains, all
the domain's entities, all relations which references this domain (only),
and all indexes involving the domain.
e: Entity; r: Relship;
es: EntitySet← QDomainSubset[d]; -- all entities
al: LIST OF Attribute← VL2EL[QGetPList[d, aTypeOf] ]; -- reffing attr's
supers: RelshipSet← QRelationSubset[dSubType, LIST[[dSubTypeIs, d]] ]; -- supers
dIndex: Index; subDomains: LIST OF Domain;
[nameIndex: dIndex, subDomains: subDomains]← GetCachedDomainInfo[d];
IF subDomains#
NIL
THEN
ERROR Error[NotImplemented]; -- must destroy the domain's subdomains first!
DBStorage.DeleteFromIndex[
GetDomainIndex[QSegmentOf[d]], ConvertToUpper[QNameOf[d]], d];
WHILE (r← QNextRelship[supers])#
NIL
DO
destroy any links to super domains
SafeSetF[r, dSubTypeOf, NIL];
SafeSetF[r, dSubTypeIs, NIL];
DBStorage.DestroyTuple[r];
ENDLOOP;
QReleaseRelshipSet[supers];
WHILE (e← QNextEntity[es])#NIL DO QDestroyEntity[e] ENDLOOP;
QReleaseEntitySet[es];
FOR alT:
LIST
OF Attribute← al, alT.rest
UNTIL alT=
NIL
DO
IF
NOT Null[alT.first]
THEN
-- this check needed 'cause two attrs of reln may ref d
{reln: Relation← V2E[SafeGetP[alT.first, aRelationIs]]; QDestroyRelation[reln]};
ENDLOOP;
[]← SafeSetP[d, dIndexProp, NIL];
QDestroyIndex[dIndex];
DestroyDictionaryEntity[d]; -- destroys any user links to d, all ours should be gone
};
QDestroyRelation:
PUBLIC
PROC [r: Relation] = {
Destroys a relation, its attributes, and all of its relships.
rel: Relship;
rs: RelshipSet← QRelationSubset[r, NIL];
al: LIST OF Attribute← VL2EL[QGetPList[r, aRelationOf] ]; -- R's attributes
WHILE (rel← QNextRelship[rs])#NIL DO QDestroyRelship[rel] ENDLOOP; -- destroy relships
QReleaseRelshipSet[rs];
FOR alT:
LIST
OF Attribute← al, alT.rest
UNTIL alT=
NIL
DO
Destroy attributes: must nil out any refs to domains & relations (to kill back refs)
[]← SafeSetP[alT.first, aRelationIs, NIL];
[]← SafeSetP[alT.first, aTypeEntityProp, NIL];
[]← SafeSetP[alT.first, aDomainIs, NIL];
DBStorage.DestroyTuple[alT.first];
ENDLOOP;
DBStorage.DeleteFromIndex[
GetRelationIndex[QSegmentOf[r]], ConvertToUpper[QNameOf[r]], r];
DBModelPrivate.FlushCaches[];
DestroyDictionaryEntity[r];
};
QDestroyAttribute:
PUBLIC
PROC[a: Attribute] =
{ ERROR Error[NotImplemented] };
QDestroySubType:
PUBLIC
PROC [of, is: Domain] = {
Need only delete the subtype tuple, st. There should be no refs to it, and we
leave it to the user to insure that no existing relships now become illegal.
However, we must explicitly set sub and super to NIL to destroy SL's back-refs.
st: Relship;
rs: RelshipSet← QRelationSubset[dSubType, LIST[ [dSubTypeIs, is], [dSubTypeOf, of] ] ];
IF (st← QNextRelship[rs])=
NIL
THEN
It's not a subType, just return and leave it that way
{QReleaseRelshipSet[rs]; RETURN};
QReleaseRelshipSet[rs];
SafeSetF[st, dSubTypeOf, NIL];
SafeSetF[st, dSubTypeIs, NIL];
DBStorage.DestroyTuple[st];
};
QDestroyIndex:
PUBLIC
PROC[i: Index] = {
Used to destroy index on domain or on relation.
Warning: must not destroy index on domain w/o destroying domain, since still refs index.
ifs: LIST OF IndexFactor← VL2EL[QGetPList[i, ifIndexOf]];
DBStorage.DestroyIndex[i];
FOR ifsT:
LIST
OF IndexFactor← ifs, ifsT.rest
UNTIL ifsT=
NIL
DO
[]← SafeSetP[ifsT.first, ifIndexIs, NIL]; DBStorage.DestroyTuple[ifsT.first] ENDLOOP;
DBModelPrivate.FlushCaches[];
DestroyDictionaryEntity[i];
};
DestroyDictionaryEntity:
PROC[e: Entity] = {
Destroy a domain, relation, attribute, index, or index factor entity.
Also destroys any group links to the entity: we don't know all the possible links to
the entity, now that we allow client-defined relations to reference dictionary entities.
We assume caller has destroyed any dependent entities and index entries, however.
DestroyLinksTo[e];
DBStorage.DestroyTuple[e];
};
SameIndex:
PROC[ i: Index, nl: AttributeList]
RETURNS[
BOOLEAN] = {
Determines whether the index factors of the given index, when compared in order to the
Attributes in nl, are the same.
a: Attribute;
if: IndexFactor;
count: INT← 0;
ifs: LIST OF Attribute← VL2EL[QGetPList[i, ifIndexOf]];
FOR nlT: AttributeList ← nl, nlT.rest
UNTIL nlT=
NIL
DO
IF ifs=
NIL
THEN
-- there more attributes in nl than index factors in ifs
RETURN[FALSE];
a← nlT.first;
if← ifs.first; ifs← ifs.rest;
IF count#V2I[SafeGetP[if, ifOrdinalPositionIs]] THEN ERROR InternalError;
IF NOT Eq[a, V2E[SafeGetP[if, ifAttributeIs]]] THEN RETURN[FALSE];
count← count+1;
ENDLOOP;
IF ifs#NIL THEN RETURN[FALSE]; -- there were more index factors than attributes in nl
RETURN[TRUE]
};
Changed by Rick on 26-Nov-81 13:38:45: Must check that no subdomains for surrogate relation optimization.
Changed by Rick on 26-Nov-81 16:20:04: Open/Create Database now get server from dbName instead of separate argument.
Changed by Rick on 6-Jan-82 12:25:15: EmptyDomain should not search subdomains. Don't need Nto1fromRole in normal PropertyObject (QDeclarePropertyFromAttrs).
Changed by Rick on 22-Jan-82 9:20:21: Implemented QDestroyDomain which also destroys SubType connections.
Changed by Rick on April 15, 1982 7:01 pm: CreatePropertyFromAttrs checks args.
Changed by Rick on April 24, 1982 12:16 am: QDeclareProperty uses version. QDestroyDomain also destroys any relations that directly reference the domain. QDestroyRelation needs to remove from index.
Changed by Rick on April 24, 1982 5:50 pm: QDestroyRelation must nil out any domain refs when destroying attributes else leave dangling ref in group. It must also FlushAttributeCache.
Changed by Rick on April 28, 1982 10:47 am: upper-case domain/relation index entries.
Changed by Rick on May 4, 1982 1:01 pm: DBStorage.CloseCommunication no longer does CloseDatabase, so call this explicitly now. OpenDatabase now calls CloseCommunication when the VersionProblem error is raised, to leave things in a consistent (unopened) state. Generate RelationOrDomainSubsetsStillOpen at end of CloseDatabase so works properly if client proceeds OR resumes.
Changed by Rick on May 6, 1982 6:39 pm: Forgot to call ConvertToUpper when removing domains and relations from indexes. Moved DestroyIndex here from DBViewSystemImpl.
Changed by Rick on July 30, 1982 1:56 pm: Changed DeclareSubType to do more thorough checking of given sub and super. Changed DestroyDomain to run faster, by destroying the whole B-Tree index at once. This necessitated changing DestroyEntity in DBViewBasicImpl to ignore the B-Tree entry removal if it can't find a B-Tree for the domain.
Changed by Rick on August 1, 1982 11:15 am: Check for databaseOpen on DeclareRelation, DeclareDomain, CloseDatabase, AbortTransaction.
Changed by Rick on November 12, 1982 2:26 pm: New properties, indices, etc. Added DeclareIndex. Added assignment of aDomainProp in DeclareAttribute.
Changed by Rick on December 13, 1982 8:45 pm: Changes for segments and various other cleanups. Indices not yet tested, however. Modified/added the global database, transaction, and segment procedures. Modified DeclareRelation and DeclareDomain to take and use segment argument. Changed BOOL databaseOpen=>initialized.
Changed by Rick on January 16, 1983 3:27 pm: bug in DeclareIndex if index existed.
Changed by Rick on February 1, 1983 8:50 am: Made all Declare procedures flush caches, just for safety; DeclareIndex was invalidating the relation info cache without flushing it! Declaring and Destroying Subtypes should be ok, for now, since they're not cached.
Changed by Rick on February 18, 1983 4:57 pm: Forgot to nil out ref to relation when destroying attributes in DestroyRelation.
Changed by Rick Cattell on March 15, 1983 10:04 am: Domain caching.
Changed by Willie-Sue on February 14, 1985: added useTrans argument to QEraseSegment; also made it do a MarkTransaction instead of a Close. Added QGetBuiltinSegments and some segment numbers. Added tioga formatting.