File: DB.mesa
Copyright © 1985 by Xerox Corporation. All rights reserved.
Contents: Cedar program interface to Cypress database system
Created by: Rick Cattell, July 8, 1981 (as DBView interface)
Rick Cattell, November 8, 1983 10:20 am
Eric Bier, August 7, 1981 15:30:15
Willie-Sue, February 22, 1985 9:18:47 am PST
Donahue, March 10, 1986 8:50:10 am PST
Widom, September 6, 1985 2:16:58 pm PDT
DIRECTORY
AlpineEnvironment USING [LockOption],
BasicTime USING[GMT],
Basics USING[LowHalf],
DBCommon,
DBDefs,
Rope USING[ROPE];
DB: CEDAR DEFINITIONS IMPORTS Basics = BEGIN
OPEN DBDefs, DBCommon;
ROPE: TYPE = Rope.ROPE;
Basic type definitions
Types
FieldSpec: TYPE = REF FieldSpecObject;
FieldSpecObject: TYPE = RECORD[fields: SEQUENCE count: CARDINAL OF Field];
Field: TYPE = RECORD[name: ROPE, type: TypeCode, lengthHint: CARDINAL ← 0];
Index: TYPE = DBDefs.Index;
Errors
Error: ERROR [code: ErrorCode];
Programming error by client
ErrorCode: TYPE = {
AlreadyExists, -- DeclareEntity, DeclareRelship: already exists, but version=NewOnly
BadUserPassword, -- Could not authenticate user
CannotDefaultSegment, -- Segment name not built-in to Cypress, and no segment # given
DatabaseNotInitialized, -- Attempt to perform operation without calling DB.Initialize
DictionaryUpdate, -- Attempt to modify a dictionary relship or entity:
EntityOrRelshipSetsOpen, -- Excessive Domain/RelationSubsets still open (CloseTransaction).
DirectoryNotFound, -- Directory specified in segment name not found on file server
FileNotFound, -- No existing segment found with given file name, and version=OldOnly
InvalidSchema, -- The domain, relation, or attribute object being used is out of date
IllegalAttribute, -- Attribute not of the given relship's Relation or not attribute
IllegalConstraint, -- Constraint specification had value of incorrect type or had wrong length
IllegalDomain, -- Argument is not actually a domain
IllegalFileName, -- No directory or machine given for segment, or invalid chars in name
IllegalEntity, -- Argument to GetP, or etc., is not an Entity
IllegalIndex,
IllegalKeySpecification, -- A field appears in two or more keys for a relation
IllegalRelship, -- Argument to GetF, or etc., is not a Relship
IllegalRelation, -- Argument is not a relation
IllegalString, -- Nulls not allowed in ROPEs passed to the database system
IllegalSuperType, -- Can't define subtype of domain that already has entities
MismatchedAttributeValueType, -- Value not same type as required (SetF)
MismatchedExistingRelation, -- Existing relation is different (DeclareRelation)
MismatchedExistingSegment, -- Existing segment has different # or name
MismatchedValueType,
MultipleMatch, -- More than one relationship satisfied avl on DeclareRelship.
NonUniqueEntityName, -- Entity in domain with that name already exists
NonUniqueKeyValue, -- Relship already exists with same value for a key attribute
NotFound, -- Version is OldOnly but no such Entity, Relation, or etc found
NotImplemented, -- Action requested is not currently implemented
NILArgument, -- Attempt to perform operation on NIL argument
NullifiedArgument, -- Entity or relationship has been deleted, or invalidated by trans abort
ProtectionViolation, -- Read or write to segment file not permitted this user.
QuotaExceeded, -- Database too big for Alpine page quota of segment's directory
SegmentNotDeclared, -- Attempt to open transaction on segment with no DeclareSegment
ServerNotFound, -- File server does not exist or does not respond
TransactionNotOpen, -- Attempt to perform operation with no transaction open
TransactionAlreadyOpen, -- Attempt to open transaction on segment already associated w/one
WriteNotAllowed, -- Attempt to write data but DeclareSegment specified read-only
WrongNumberOfFields, -- attempt to create relship with ill-formed list of initial values
Unknown -- Unknown or not yet assigned error code
};
Aborted: ERROR;
The transaction on the segment manipulated by the last operation was aborted. It is necessary to call AbortTransaction ; OpenTransaction before performing any further operations on the segment
Failure: ERROR [what: ATOM, info: ROPE];
Unrecoverable error, out of resources or bad disk
InternalError: ERROR;
Bug in database system, hardware, or operating system
Global operations
Initialize: PROC[nCachePages: NAT ← 512, cacheFileName: ROPENIL];
Initializes the database system for use. Only one client may open an instance of the database package at a time. The Initialize operation is idempotent: subsequent calls after the first return with no effect.
System parameters are initialized as follows:
nCachePages says how many pages of virtual memory to use for the DB page cache
cacheFileName says what file to use for the database cache (DBSegment.VM default)
DeclareSegment: PROC[filePath: ROPE, segment: Segment, number: SegmentIndex ← 0, lock: AlpineEnvironment.LockOption ← [intendWrite, wait], readonly: BOOLFALSE, createIfNotFound: BOOLTRUE, nPagesInitial, nPagesPerExtent: INT ← 64];
Declares a segment file whose full path name is filePath; the file is not actually opened until a transaction is opened for this segment. If createIfNotFound is true, the segment is created if it does not already exist (at the time of the OpenTransaction call).
A segment number must also be passed in if a new segment might be created. This number may be defaulted for applications whose number has been added to the database system's default table (Walnut, Grapenut, Squirrel, Test), else see database administrator. This crock will be fixed in some near future implementation of Cypress, I hope! Until then, GetBuiltInSegments will at least give a list of the segments that have been hard-wired into Cypress.
If readonly = TRUE, then no writes may be performed on data in the segment.
DeclareSegment may be called more than once, however the (resumable) warning Error TransactionAlreadyOpen will be generated if a transaction is already open on the segment. If version # NewOnly, these additional calls to DeclareSegment are no-ops. If version = NewOnly, however, the segment file will be re-initialized on the next call to OpenTransaction. nPagesInitial and nPagesPerExtent tell Cypress how to create and extend the file size of the segment as needed for more data.
Errors:
IllegalFileName if directory or server name was omitted
TransactionAlreadyOpen if transaction already open
EraseSegment: PROC[segment: Segment, useTrans: TransactionHandle ← NIL] RETURNS[trans: TransactionHandle];
Erases all the relations and domains in the given segment. The segment must have been declared. A new transaction on the segment is opened, committed, and returned.
GetSegmentInfo: PROC[Segment] RETURNS[filePath: ROPE, readOnly: BOOL];
Returns information about the given segment. The filePath will be NIL if no such segment is declared.
OpenTransaction: PROC[segment: Segment, useTrans: TransactionHandle ← NIL] RETURNS[trans: TransactionHandle, schemaInvalid: BOOL];
Assocates the client-supplied useTrans transaction (or a newly created one if useTrans = NIL) with all operations on the given segment. The opened transaction is returned to the client (useTrans if it's non-NIL); if schemaInvalid is TRUE, then any cached Domain or Relation object are invalid.
Errors:
TransactionAlreadyOpen if transaction is already associated with this segment (must close)
FileNotFound if a segment under this transaction (see DeclareSegment) not found
ProtectionViolation if a segment file under this transaction cannot be accessed
MismatchedExistingSegment if existing segment file has different number or name
SegmentsForTransaction: PROC[trans: TransactionHandle] RETURNS[SegmentList];
Returns all of the segments currently sharing the transaction
MarkTransaction: PROC[trans: TransactionHandle];
Makes all of the interactions of the transaction permanent and starts a new transaction.
AbortTransaction: PROC[trans: TransactionHandle];
Discard all changes since the last MarkTransaction (or OpenTransaction).
CloseTransaction: PROC[trans: TransactionHandle];
Does MarkTransaction but leaves no transaction open.
-- The next procedure allows the database transaction to be controlled from the outside.
MakeTransHandle: PROC[trans: DBCommon.Transaction] RETURNS[handle: TransactionHandle];
Make a transaction handle from an Alpine transaction; this handle has no segments attached to it; ie., when the transaction is committed, no attempt will be made to maintain consistency of any schema caches.
Schema-definition and -interrogation operations
The Operations on Domains
DeclareDomain: PROC[name: ROPE, segment: Segment] RETURNS[d: Domain];
Creates a domain of this name in the given segment, or fetches it if it already exists.
LookupDomain: PROC[name: ROPE, segment: Segment] RETURNS[d: Domain];
If the domain does not exist in the segment, then the result will be NIL
DestroyDomain: PROC[d: Domain];
Destroys a domain, all its entities, and all relationships that reference entities in d. It is the client's responsibility to destroy any sub-domains if desired.
DomainName: PROC[d: Domain] RETURNS[name: ROPE, segment: Segment];
Returns the name of a domain and the segment it belongs to.
NullDomain: PROC[d: Domain] RETURNS[BOOL];
True iff the domain has been nullified or is NIL
DomainEq: PROC[d1, d2: Domain] RETURNS[BOOL];
Returns TRUE iff d1 and d2 reference the same domain in the same segment, or are both null.
DomainsByName: PROC[segment: Segment, lowName, highName: ROPE, start: FirstLast] RETURNS[ds: DomainSet];
Provides a domain set that enumerates domains in the given segment. Enumerates only those domains whose name is lexicographically >= lowName and <= highName. The start parameter allows the enumeration to start at the beginning or end: If start = last, the enumeration will start with the domain with the lexicographically largest name, i.e. NextDomain will return NIL and successive calls to PrevDomain will return the domains. The converse is true if start=first.
NextDomain: PROC[ds: DomainSet] RETURNS[d: Domain];
Returns NIL when no more domains in the set.
PrevDomain: PROC[ds: DomainSet] RETURNS[d: Domain];
Can be used to back up a DomainSet. Returns NIL when back to beginning.
ReleaseDomainSet: PROC[ds: DomainSet];
Should be called when client is finished with a DomainSet.
DeclareSubType: PROC[of, is: Domain];
Defines a subtype relationship between two domains => entities of subtype can participate in any attribute accepting entities of the supertype. Currently, may not do this if "of" domain has any relations defined on it or any tuples in it.
The two domains must be in the same segment or error code MismatchedSegment will be generated.
DestroySubType: PROC[of, is: Domain];
Destroys the subtype relationship between the two domains.
SuperType: PROC[d: Domain] RETURNS[super: Domain];
Returns the supertype of a given domain (if one has been previously defined using DeclareSubType).
SubTypes: PROC[d: Domain] RETURNS[subs: DomainSet];
Returns the subtypes of a given domain (if any have been previously defined using DeclareSubType).
TypeForDomain: PROC[d: Domain] RETURNS[type: TypeCode];
Maps the domain into the type code needed to declare attributes.
RelationsOf: PROC[d: Domain] RETURNS[rs: RelationSet];
Produce a set of all relations that (potentially) have fields that reference entities in the given domain.
The Operations on Relations
DeclareRelation: PROC[name: ROPE, segment: Segment, fields: FieldSpec, keys: LIST OF Index] RETURNS[r: Relation];
Creates a relation of the given name in the given segment. The FieldSequence gives a description of the fields that each tuple in the relation will have; the keys give a description of the keys of each relation — an index is maintained for each key.
LookupRelation: PROC[name: ROPE, segment: Segment] RETURNS[r: Relation];
If no relation with the given name exists in the segment, then r will be NIL
FieldCount: PROC[r: Relation] RETURNS[count: CARDINAL];
Return the number of fields in the relation r
Fields: PROC[r: Relation] RETURNS[fieldSpec: FieldSpec];
Return the description of all of the fields of the relation. (The lengthHint of each field will be 0, since this information is not stored in the schema, but is only used when the relation is defined)
DestroyRelation: PROC [r: Relation];
Destroys a relation, and all of its relationships.
RelationInfo: PROC[r: Relation] RETURNS[name: ROPE, segment: Segment];
Returns the name of a relation and the segment it belongs to
NullRelation: PROC[r: Relation] RETURNS[BOOL];
True iff the relation has been nullified or is NIL
RelationEq: PROC[r1, r2: Relation] RETURNS[BOOL];
Returns TRUE iff r1 and r2 reference the same relation in the same segment, or are both null.
RelationsByName: PROC[segment: Segment, lowName, highName: ROPE, start: FirstLast] RETURNS[rs: RelationSet];
Provides a relation set that enumerates relations in the given segment. Enumerates only those relations whose name is lexicographically >= lowName and <= highName. The start parameter allows the enumeration to start at the beginning or end: If start = last, the enumeration will start with the relation with the lexicographically largest name, i.e. NextRelation will return NIL and successive calls to PrevRelation will return the relations. The converse is true if start = first.
NextRelation: PROC[rs: RelationSet] RETURNS[Relation];
Returns NIL when no more relations in the set.
PrevRelation: PROC[rs: RelationSet] RETURNS[Relation];
Can be used to back up a RelationSet. Returns NIL when back to beginning.
ReleaseRelationSet: PROC[rs: RelationSet];
Should be called when client is finished with a RelationSet.
NameToField: PROC[r: Relation, name: ROPE] RETURNS[exists: BOOL, pos: CARDINAL];
Which field of the relation r has the specified name? If exists is FALSE, then the value of pos is meaningless
FieldInfo: PROC[r: Relation, pos: CARDINAL] RETURNS[field: Field];
Returns the name of the given attribute, the relation it belongs to, its position in the relation, the type and the uniqueness.
DeclareIndex: PROC[r: Relation, index: Index];
Creates an index on the given attributes, to make RelationSubset go faster. Note: in the current implementation, indices cannot be created after tuples have been added to the relation.
DestroyIndex: PROC[r: Relation, index: Index];
Destroy a previously declared index.
Keys: PROC[r: Relation] RETURNS[keys: LIST OF Index];
Returns a list of all of the keys of the relation.
Indices: PROC[r: Relation] RETURNS[indices: LIST OF Index];
Returns a list of all non-key (client-specified) indices of the relation.
Primitive operations
DeclareEntity: PROC[d: Domain, name: ROPE] RETURNS[e: Entity];
Creates or returns an existing entity in domain d with the given name. Case is not significant in comparing entity names for equality, however the letter cases used when the entity is created will faithfully be returned by calls to EntityInfo.
Errors:
IllegalDomain if d is not a legal domain
LookupEntity: PROC[d: Domain, name: ROPE] RETURNS[e: Entity];
If no entity exists with the given name in the domain, e will be NIL.
Errors:
IllegalDomain if d is not a legal domain
DestroyEntity: PROC[e: Entity];
Removes entity from its domain and destroys all relationships referencing the entity.
EntityInfo: PROC[e: Entity] RETURNS[name: ROPE, domain: Domain];
Returns name of entity e and the domain it belongs to. Allowed on relation, attribute, and domain entities too.
EntityEq: PROC[e1: Entity, e2: Entity] RETURNS[BOOL];
Returns TRUE iff e1 and e2 reference the same entity in the same segment, or are both null.
NullEntity: PROC[e: Entity] RETURNS[BOOL];
Returns TRUE iff e is NIL or has become invalidated by deletion or transaction abort.
CreateRelship: PROC[r: Relation, init: ValueSequence] RETURNS[relship: Relship];
Creates a relationship in relation r with the given initial values.
LookupRelship: PROC[r: Relation, key: Index, val: ValueSequence] RETURNS[relship: Relship];
Return the relship (if any) that has the value sequence as the given key.
LookupWithSimpleKey: PROC[r: Relation, key: Index, val: Value] RETURNS[relship: Relship];
If the key field is only one element long, this procedure can be used instead of the one above — it obviates some of the problems of sequences in Cedar.
DestroyRelship: PROC[t: Relship];
Removes relationship from its relation and destroys it.
GetF: PROC[t: Relship, field: CARDINAL] RETURNS[Value];
Returns the given attribute value. Returns the undefined value for one of these types if attribute not yet initialized, see documentation. Returns NIL if attribute references an entity in an un-opened segment.
SetF: PROC[t: Relship, field: CARDINAL, v: Value];
Sets attribute a of relationship t to given numeric, string, or entity value.
GetP: PROC[e: Entity, r: Relation, field: CARDINAL] RETURNS[Value];
If the first field of r has the type of the domain of e and is also a key, this procedure returns the value of the given field of the relationship in r having e as its key — the entity e determines the corresponding relationship in r. (This is primarily a convenience operation, although it is somewhat more efficient than the corresponding GetF[Lookup[ . . .] . . .])
SetP: PROC[e: Entity, r: Relation, field: CARDINAL, v: Value];
Again, a convenience operation to set fields of relationships that have entity-valued keys.
RelationOf: PROC[t: Relship] RETURNS[Relation];
Returns the relation to which the relationship belongs.
RelshipEq: PROC[r1: Relship, r2: Relship] RETURNS[BOOL];
Returns TRUE iff r1 and r2 reference the same relship in the same segment, or are both null.
NullRelship: PROC[r: Relship] RETURNS[BOOL];
Returns TRUE iff r is NIL or has become invalidated by deletion or transaction abort.
Aggregate operations
RelationSubset: PROC[ r: Relation, index: Index, constraint: Constraint, start: FirstLast] RETURNS[RelshipSet];
Used with NextRelship below to index through relationships in a relation whose fields match specified value ranges. A NIL constraint gives all of r. The index and the constraints must match in size and the constraints must be consistent with the type of the index. The elements of the enumeration will be produced in the order in which they are found in the index. Note that this index may be either a key specification or another non-key client-specified index.
RelshipsForEntity: PROC[e: Entity] RETURNS[rs: RelshipSet];
Enumerate all of the relships that reference e in some field. These relationships may span a number of different relations.
RelshipsWithEntityField: PROC[r: Relation, field: CARDINAL, val: Entity] RETURNS[rs: RelshipSet];
Enumerate all of the relships that have the value val for the given field (where the field must be entity-valued). Even if the relation does not have a key on the given field, this enumeration will be fast.
NextRelship: PROC[rs: RelshipSet] RETURNS[Relship];
Returns NIL when there are no more entities in the set.
PrevRelship: PROC[rs: RelshipSet] RETURNS[Relship];
Can be used to back up a RelshipSet. Returns NIL when back to beginning.
ReleaseRelshipSet: PROC[rs: RelshipSet];
Should be called when client is finished with a RelshipSet.
DomainSubset: PROC[d: Domain, lowName, highName: ROPENIL, start: FirstLast] RETURNS[EntitySet];
Used with NextEntity to index through entities in a domain (if d is one of the system domains, then the segment argument is used to disambiguate). If lowName and highName are non-NIL, enumerates only those entities whose name is lexicographically >= lowName and <= highName. As with RelationSubset, the start parameter allows the enumeration to start at the beginning or end: If start = last, the enumeration will start with the entity with the lexicographically largest name, i.e. NextEntity will return NIL and successive calls to PrevEntity will return the entities. The converse is true if start=first. If only highName is NIL, it defaults to lowName, i.e. we will search for the entity whose name exactly equals lowName.
NextEntity: PROC[es: EntitySet] RETURNS[Entity];
Returns NIL when no more entities in the set.
PrevEntity: PROC[es: EntitySet] RETURNS[Entity];
Can be used to back up an EntitySet. Returns NIL when back to beginning.
ReleaseEntitySet: PROC[es: EntitySet];
Should be called when client is finished with an EntitySet, to release resources.
Miscellaneous operations
Converting from Lists to Sequences (why, oh why isn't this part of the language?)
L2VS: PROC[vals: LIST OF Value] RETURNS[seq: ValueSequence];
L2FS: PROC[vals: LIST OF Field] RETURNS[seq: FieldSpec];
L2F: PROC[vals: LIST OF CARDINAL] RETURNS[seq: FieldSequence];
L2C: PROC[vals: LIST OF ValueConstraint] RETURNS[seq: Constraint];
Conversion of values to and from Mesa types
I2V: PROC[i: INT] RETURNS[Value] =
INLINE BEGIN RETURN[[integer[i]]] END;
B2V: PROC[b: BOOL] RETURNS[Value] =
INLINE BEGIN RETURN[[boolean[b]]] END;
U2V: PROC[u: UNSPECIFIED] RETURNS[Value] =
Use for storing an enumerated type such as Uniqueness as an INT
INLINE BEGIN RETURN[[integer[LOOPHOLE[u, CARDINAL]]]] END;
S2V: PROC[s: ROPE] RETURNS[Value] =
INLINE BEGIN RETURN[[rope[s]]] END;
E2V: PROC[e: Entity] RETURNS[Value] =
INLINE BEGIN RETURN[[entity[e]]] END;
T2V: PROC[t: BasicTime.GMT] RETURNS[Value] =
INLINE BEGIN RETURN[[time[t]]] END;
Procedures to do implicit NARROWs.
V2B: PROC[v: Value] RETURNS[value: BOOL] =
TRUSTED INLINE { WITH v: v SELECT FROM boolean => value ← v.value; ENDCASE => ERROR Error[MismatchedValueType] };
V2I: PROC[v: Value] RETURNS[value: INT] =
TRUSTED INLINE { WITH v: v SELECT FROM integer => value ← v.value; ENDCASE => ERROR Error[MismatchedValueType] };
V2S: PROC[v: Value] RETURNS[value: ROPE] =
TRUSTED INLINE { WITH v: v SELECT FROM rope => value ← v.value; ENDCASE => ERROR Error[MismatchedValueType] };
V2U: PROC[v: Value] RETURNS[value: UNSPECIFIED] =
TRUSTED INLINE { WITH v: v SELECT FROM integer => value ← Basics.LowHalf[LOOPHOLE[v.value]]; ENDCASE => ERROR Error[MismatchedValueType] };
V2T: PROC[v: Value] RETURNS[value: BasicTime.GMT] =
TRUSTED INLINE { WITH v: v SELECT FROM time => value ← v.value; ENDCASE => ERROR Error[MismatchedValueType] };
V2E: PROC[v: Value] RETURNS[value: Entity] =
TRUSTED INLINE { RETURN[WITH v: v SELECT FROM entity => v.value, ENDCASE => ERROR Error[MismatchedValueType] ] };
END.