22 Navigation
The University database
Let u be a program variable of type University. It can be
thought of as the database being queried.
University u;
The aim for the chapters in this part is to explain how to write read-only queries on u.
The emphasis will be on using examples to illustrate how it works, without delving into the
formal denotational semantics.
Path expressions
A path expression is an expression which can appear in Predica programs and in queries:
u.student.name
u.course.[advanced].code
u.course.(code, title)
u.student.name as studentName
u.course.session.time.(day, startMinute)
u.course.let c = this in c.prerequisite.code
u.course.assessment.exam.durationMinutes.Σsum
u.course.[advanced]⇒session.time.day
Path expressions are analogous to relational algebra expressions used to express queries on a relational database.
In our examples path expressions start at u then typically follow attributes through the schema.
Frames
Here we give an informal description of frames and explain the notation used to write them. They underpin the semantics of Predica.
Consider the following set builder notation for the points on the unit circle:
{ (X,Y) ∈ ℝ² | X² + Y² = 1 }
In an analogous way we can use a set builder notation to denote a relation with two attributes
code and title which represents a query on the university database:
{ { (code=X:String), (title=T:String) }
where ∃C:Course
u.course(C) ∧ C.code(X) ∧ C.title(T) }
This is an expression of the form {R where Ψ}.
In this case we are ranging over the variables in R.
That is not always the case, the more general form is written {R for F where Ψ}.
The following illustrates use of for in a set builder notation:
{ r(X,Y) for X:A,Y:B where p(X,Y) }
In the formal denotational semantics a path expression denotes a frame (R,F,Ψ),
i.e. the three components of a set builder notation of an n-ary relation.
It is convenient to write a frame using the notation R for F where Ψ or sometimes just
R where Ψ.
For example, the path expression u.student denotes the frame:
student=S:Student where u.student(S)
Navigation
The simplest paths select one kind of object from the university.
u.student
u.course
u.department
u.enrolment
A path is read from left to right. The expression u.student.name starts with the
university, reaches its students, and then reaches each student's name.
u.student.name
The same idea works for longer paths. The following expression starts at the university, reaches students, reaches their enrolments, reaches the courses for those enrolments, and finally reaches the course codes.
u.student.enrolment.course.code
This is relational-join-like traversal: the path crosses from students to enrolments to courses by following declared relationships instead of writing the relational joins explicitly.
Paths can move through derived attributes in the same way as stored attributes.
Student.enrolled is calculated from Student.enrolment.course, so these two
expressions reach the same courses. Path expressions do not need to distinguish
between base attributes and derived attributes.
u.student.enrolment.course
u.student.enrolled
A path may reach many values. The expression u.course.session.room reaches the
rooms used by course sessions; it is not a single room unless the data and the
schema make it single-valued.
u.course.session.room
Navigation can also follow relationships that were written in the reverse direction
for convenience. Department.course is derived from Course.department, but it
still reads as ordinary navigation from a department to the courses it offers.
u.department.course.code
The important habit is to keep track of the current kind of value at each step. In
u.student.enrolment.course.department.name, the path passes through a university,
students, enrolments, courses, departments, and finally department names.
u.student.enrolment.course.department.name
For example, the path u.student.enrolment.course.code corresponds to a chain of
relationships in logic:
code=X:String
where
∃S:Student ∃E:Enrolment ∃C:Course
u.student(S)
∧ S.enrolment(E)
∧ E.course(C)
∧ C.code(X)
The result attribute is code, bound to X. The variables S, E and C are
the intermediate values reached by the path. Each dotted step contributes another
predicate, and the intermediate values are existentially quantified. This is why
a dotted path should be read as relational navigation, not as ordinary object
property access.
Navigation is uniform across type kinds
This is an important unification in Predica, and may be surprising at first. Base and derived attributes of value types, entity types and quotient types, fields of structural types, and attributes of enum types and variant types are all navigated in the same way. Enum and variant attributes are regarded as always present as unary predicates, which is why they can be used in path expressions without conditionals. Their denotations into logic work identically as well.
For example:
u.course.session.time.startMinute
u.course.session.time.day.monday
u.course.assessment.exam.durationMinutes
u.student.enrolled.code
The first path uses attributes of entity and value types. The second uses an enum
attribute. The third uses a variant attribute. The fourth uses the derived
attribute Student.enrolled. All of these are ordinary path expressions, and
their denotations as frames have the same form.