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, May 28, 1986 11:55:46 am PDT
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
ROPE: TYPE = Rope.ROPE;
Basic definitions
Types
Most of these type definitions duplicate those found in the DBDefs and DBCommon interfaces to make things a little less confusing for clients. One day soon these will be turned into opaque definitions (probably) to make the code somewhat more modular
TypeCode: TYPE = DBDefs.TypeCode;
FieldSpec: TYPE = REF FieldSpecObject;
FieldSpecObject: TYPE = RECORD[fields: SEQUENCE count: CARDINAL OF Field];
TypeSpec: TYPE = RECORD[ type: SELECT kind: * FROM direct => [code: TypeCode], indirect => [ code: PROC[] RETURNS [TypeCode] ] ENDCASE ] ← Any;
Note: type specification may be a nullary procedure (rather than simply being a type code) so that the client can define the fields of a relation before all of the domains that it may depend on have been declared
Field: TYPE = RECORD[name: ROPE, type: TypeSpec, lengthHint: CARDINAL ← 0];
Integer: TypeSpec = [direct[code: DBDefs.intType]];
String: TypeSpec = [direct[code: DBDefs.ropeType]];
Boolean: TypeSpec = [direct[code: DBDefs.boolType]];
Time: TypeSpec = [direct[code: DBDefs.timeType]];
Any: TypeSpec = [direct[code: DBDefs.anyDomainType]];
FieldSequence: TYPE = DBDefs.FieldSequence;
ValueSequence: TYPE = DBDefs.ValueSequence;
Value: TYPE = DBDefs.Value;
Index: TYPE = DBDefs.Index;
Segment: TYPE = DBCommon.Segment;
SegmentList: TYPE = DBCommon.SegmentList;
SegmentIndex: TYPE = DBCommon.SegmentIndex;
FirstLast: TYPE = DBCommon.FirstLast;
TransactionHandle: TYPE = DBCommon.TransactionHandle;
Transaction: TYPE = DBCommon.Transaction;
Domain: TYPE = DBDefs.Domain;
DomainSet: TYPE = DBDefs.DomainSet;
Relation: TYPE = DBDefs.Relation;
RelationSet: TYPE = DBDefs.RelationSet;
Entity: TYPE = DBDefs.Entity;
EntitySet: TYPE = DBDefs.EntitySet;
Relship: TYPE = DBDefs.Relship;
Constraint: TYPE = DBDefs.Constraint;
ValueConstraint: TYPE = DBDefs.ValueConstraint;
RelshipSet: TYPE = DBDefs.RelshipSet;
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
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
IllegalProperty, -- An attempt was made to declare a property relation with a non-entity valued first field
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
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.
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.
DomainInfo: 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 ← First] 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] RETURNS[r: Relation];
Creates a relation of the given name in the given segment. The FieldSpec gives a description of the fields that each tuple in the relation will have.
DeclareProperty: PROC[name: ROPE, segment: Segment, fields: FieldSpec] RETURNS[r: Relation];
The first field of the given field specifications must be entity-valued (and not of type anyDomainType); the first field is taken as a key for the relation. Note that the GetP and SetP operations can be used on relations that have been declared as properties.
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[fields: FieldSpec];
Return the description of all of the fields of relation r.
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
FieldDescription: PROC[r: Relation, pos: CARDINAL] RETURNS[field: Field];
Returns the name and type of the given field.
RelationInfo: PROC[r: Relation] RETURNS[name: ROPE, segment: Segment];
Returns the name of a relation and the segment it belongs to
DestroyRelation: PROC [r: Relation];
Destroys a relation, and all of its relationships.
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 ← First] 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.
The Operations on Indices
DeclareKeyIndex: PROC[r: Relation, fields: FieldSequence] RETURNS[index: Index];
Creates a key index on the given fields. No two relationships in the relation will have the same key — both keys and other client-specified indices can be used to perform efficient enumeration
DeclareIndex: PROC[r: Relation, fields: FieldSequence] RETURNS[index: Index];
Creates an index on the given fields. Note: in the current implementation, indices cannot be created after tuples have been added to the relation.
KeyIndices: PROC[r: Relation] RETURNS[keyIndices: LIST OF Index];
Returns a list of all of the indices maintained on keys of the relation.
OtherIndices: PROC[r: Relation] RETURNS[indices: LIST OF Index];
Returns a list of all non-key (client-specified) indices of the relation.
DestroyIndex: PROC[r: Relation, index: Index];
Destroy a previously declared index.
Primitive operations
DeclareEntity: PROC[d: Domain, name: ROPE, newOnly: BOOLFALSE] 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
AlreadyExists if newOnly and the entity already exists in the 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.
CopyEntity: PROC[e: Entity] RETURNS[Entity];
Creates a copy of an entity such that the tuple handle for the entity will NOT be nullified when the transaction for the segment in closed. This allows an application to cache entities across transaction commits or aborts: NOTE THAT IT IS THE CLIENT'S RESPONSIBILITY TO ENSURE THAT THE TUPLEHANDLE REMAINS VALID.
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.
ChangeName: PROC[ of: Entity, to: Rope.ROPE ];
Changes the name of the Entity to be the given ROPE
ToUnderType: PROC[e: Entity];
This NARROWs the entity to the subtype (if any) from which it came; if the domain of e has no subtypes then this operation has no effect. Note: THIS DOES NOT CREATE A NEW ENTITY, BUT CHANGES THE DOMAIN OF THE ONE SUPPLIED.
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, keyIndex: Index, key: ValueSequence] RETURNS[relship: Relship];
Return the relship (if any) that has the value sequence as the given key. The index must have been declared using DeclareKeyIndex
LookupWithSimpleKey: PROC[r: Relation, keyIndex: Index, key: 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.
LookupProperty: PROC[r: Relation, e: Entity] RETURNS[relship: Relship];
If the relation r was declared to be a property, then return the relationship that has e as its key.
FirstRelship: PROC[r: Relation] RETURNS[relship: Relship];
This returns the first relationship in the relation r; it is more efficient than the corresponding RelationSubset followed by NextRelship. This is particularly useful if the client can guarantee that the relation r contains only a single relationship
DestroyRelship: PROC[t: Relship];
Removes relationship from its relation and destroys it.
CopyRelship: PROC[t: Relship] RETURNS[Relship];
Creates a copy of an relationship such that the tuple handle for the relationship will NOT be nullified when the transaction for the segment in closed. This allows an application to cache relationships across transaction commits or aborts: NOTE THAT IT IS THE CLIENT'S RESPONSIBILITY TO ENSURE THAT THE TUPLEHANDLE REMAINS VALID.
GetF: PROC[t: Relship, field: CARDINAL] RETURNS[Value];
Returns the given attribute value. If the field is a nullified entity-valued field, then the value null[] is returned.
SetF: PROC[t: Relship, field: CARDINAL, v: Value];
Sets attribute a of relationship t to given numeric, string, or entity value. The value null[] can be used to nullify entity-valued fields
GetP: PROC[e: Entity, r: Relation, field: CARDINAL] RETURNS[Value];
If the relation r was defined as a property, then get the field of the relationship in r having e as its first attribute (ERROR DB.Error[NotFound] will be raised if no such relationship exists. (This is more efficient than the corresponding GetF[LookupProperty[ . . .] . . .]).
SetP: PROC[e: Entity, r: Relation, field: CARDINAL, v: Value];
If the relation r was defined as a property, then set the field of the relationship in r having e as its first attribute (ERROR DB.Error[NotFound] will be raised if no such relationship exists. (This is more efficient than the corresponding SetF[LookupProperty[ . . .] . . .]).
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 ← First] 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 ← First] 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, null => NIL, ENDCASE => ERROR Error[MismatchedValueType] ] };
END.