O2 OODB System

The goal of this file is to help you get started with O2.

Please note that the syntax in some of the examples might need minor adjustment before they will work with the current version of O2. If you find any errors, or places which are unclear, or if you have any suggestions or comments, please let us know (email to Pratik - pmukhopa@cs.ucsd.edu). Your help is greatly appreciated.

Important Notes


You will be able to run O2 only from the uape machines uape-1 through uape-50. O2 will not run on ieng9, sdcc5, or other accounts that you might have. Only one O2 server can be running on a single machine at a time. Because of this limitation on running O2, it is really important for you to remember to terminate your usage with O2 at the end of your session, by typing "o2done". Failure to do so will result in one less copy that can be run, therefore increasing the contingency of usage. So, please do not forget to type "o2done" when you are ready to take a break. Exit or logout from your account will not release O2 license automatically. Please be considerate of other groups. When all fifty machines (uape-1 - 50) are occupied, if someone is not using O2, you can still remotely login to them and start running O2.

*******************************************************************
***
Make sure to type "o2done " before logout!
*******************************************************************

Running O2


Setup your environment

Execute "prep cs132w" to access the following aliases for O2 system:

Run this script ONCE when you start O2 for the FIRST TIME. If for some reason you would like a fresh start you can run the command again. In return, you will get some info and a question like:
overwrite system o2_cs132w3 (y/n) [n] ?
Answering 'y' will clean out your database.

  • o2ser

Starts O2 server on the specific machine. You will get this feedback:
o2server is up and running !
Whenever you login and want to work on O2, you'll need to first start up the O2 server on your machine. O2 allows only one server to run on a machine at a time.

  • o2cli

Once you have the server started up, you can start up the O2 client. Client is what you will actually be running your O2 commands in (like Ingres' sql command interpreter).

  • o2done

When you're done using O2 for the day and want to log out, be sure to quit the O2 server by typing "o2done"!It's very important that you shutdown the O2 server on your machine before you log out, or else the next person logging in won't be able to run O2. If you ever get an error message telling you that an O2 server is already running, this means that either you have an O2 server already running, or someone failed to quit their O2 server properly before they logged out. Try using the termination command as described above. Remember, it's important that you quit the server before you log out.

O2 Commands

To execute an O2 command(s), end each command with a carriage return, and then hit Ctrl-D (like Ingres' "\g" command).

Use semicolons (";") to separate O2 commands.

Comments are enclosed with /* */ like in C/C++.

Some help on the syntax is available from within the O2 client interpreter by typing "help" followed by an O2 keyword. For example, to find the syntax for creating a class, type "help class".

To run/load a text file containing O2 commands, use the include syntax (#):
# "myo2file.txt"

To quit, in a new line, type:
quit
 

O2 Schema and Base

An O2 database consists of a O2 schema and a O2 base. The schema contains important information about the structure of the data such as class information, their attributes and methods, etc. The base contains the actual data that comply to that schema. A schema can have multiple bases. At a time one always works with a specific schema-base pair.

In order to work on a database, a schema and a base have to be selected. At the beginning an empty schema and an empty base must be created:
create schema YourSchemaNameHere;
create base YourBaseNameHere;

Then, tell O2 which schema that you want to use:
set schema YourSchemaNameHere;

Now, you can define your classes and methods for the schema (see "definitions" parts). When done with defining classes, confirmation is required before one can start using such classes:
confirm classes;
Whenever you add new classes, you'll need to confirm your classes again.

You can now tell O2 which base you want to use:
set base YourBaseNameHere;

Finally, we're ready to use defined classes and methods.

Later on, when you start up O2, just do:
set schema YourSchemaNameHere;
and
set base YourBaseNameHere;

to use your previously setup schema and base (including your classes, persistent names etc).

Common error situations

  • When trying to create a class:
    ERROR 13032 : Cannot modify the schema when positioned on a CONTROLLED base

    Try setting the schema with:
    set schema YourSchemaNameHere;
  • When trying to set the base:
    ERROR 13022 : Cannot open the base: the schema contains unconfirmed classes

    First confirm your classes:
    confirm classes;
    and then set your base with:

    set base YourBaseNameHere;

Examples

  • Name my schema 's1'
    create schema s1; /* first start assignment, create a new schema */
  • Define classes and methods
    confirm classes;
  • Name my base 'b1'
    create base b1;
  • Now can run code using defined classes

    /* adding a new class: */

    set schema s1;

    /* define your new class and methods */

    confirm classes;
    set base b1;

    /* now you can run code using your classes */

    /* using already-existing classes when start up O2 again */
    set schema s1;
    set base b1;

    /* now you can run code using your classes */

O2C Language

O2C language is a superset of C++ with an extended type system. This is the language we use to define classes, to write the body of their methods and to build application programs. The O2 types that can be manipulated using O2C are the following:

Atomic Types

boolean | char | string | integer | real

For each atomic types, standard operations such as + are defined.

Collection Types

Collection types are used to manage collections (sets) of objects of other types.

set( < type > ) | unique_set( < type > ) | list( < type > )

A set is a disordered collection of values of the base type. The same value can occur several times in the set.

Tuple Types

Tuple types are used to compose instances of other (simpler) types into records.

tuple( < attrib1 > : < type1 > , < attrib2 > : < type2 > , ... )

Components (attributes) of the tuple are named and can be accessed.

Class Definitions

Class definitions include:

First, let's declare a Person class with information about a person such as their name and age.

class Person
public type tuple (
  name: string,  /* data members */
  age: integer
  )
  method    /* method declarations */
    public init (n: string, a: integer),
    public print,     
    public get_age : integer
end;

The first public designates that the data members are publicly accessible. If it is omitted, then all data members are private to the class. Methods can be declared as public, private or read. Note that the "print" method takes no arguments and doesn't return anything. The "init" method is similar to a class constructor in C++. This method gets called when you create an object of this type. For instance:

o2 Person Robert = new Person("Robert", 23); /* See local variables part below */

Suppose we want to create a TA class.  A TA should be a Person, also he/she also receive a salary. Inheritance helps us understand the relationship between classes:

class TA inherit Person
public type tuple ( 
  salary:  real
  )
  method public
    get_salary : real
end;

Another class, namely Tutor, is different in the sense that each Tutor has several students (a set of students) whom he/she is responsible for. Associated with each student, there is a name, and mount of fee he/she needs to pay the Tutor.

class Tutor inherit Person
public type tuple (
  students:  set(  /* "students" is a set of ordered pairs */
    tuple( 
      name: string,
      fee:  real
    )
  ),
  salary:  real
  )
  method public
    get_salary : real
end;

Now, suppose we want to describe a person who is both a TA and a Tutor.  O2 supports multiple inheritance:

class TATutor inherit TA, Tutor
public type tuple (
 total_salary: real
 )
 method public
  compute_salary
end;

You can display your classes with the "display" command:

display classes;  /* List all classes */
display class MyClassName;  /* Show a specific class */

Local Variables

For local variable declarations, use the keyword "o2":

o2 real total= 0.0;
o2 tuple(name: string, fee:real) curr_student;
o2 Person per = new Person;

When you declare variables that represent class objects (like the "per" variable above), they're always pointers, like in Java, not the actual objects. You must use "new" to create objects. Since the object vars are pointers, you must use notation "->" instead of "." to access the data members and methods:

per->name = "Joe";
per->age = 23;

The data that we created using local variables will disappear once the piece of code that contains them finishes.

Persistent O2 Names

When we want to keep data around, we need to make it persistent by using O2's "name" keyword.  You can think of these as global variables in C/C++, except that this global data will stick around, even if you quit O2 and later on restart O2 session.  This is useful for when we want to start storing objects in our database.  For example, if we wanted to store a set of people, we'd declare a persistent set:

name People : set(Person);
name CombinedSalaries : real;

You might want to define the persistent names of your database before you define the body of the class methods, because you might need to use these persistent names in the body of these methods. Also remember that names are created in the schema, not the base.

Method Definitions

With each class, methods are associated to manipulate the value of the object. A method can have parameters that are of an arbitrary O2 type and can have a return value. The types of the parameters and the return type form the method signature. The implementation of method is the method body which is written in O2C.

Let's define the initialization method for the Person class:

method body init (n: string, a: integer) in class Person {
  self->name = n;
  self->age = a;
}

The reserved word "self" is the similar to the "this" pointer in C++.  Use "self" to refer to data members within your class methods. O2 also has a "printf" function similar to "printf" in C.

method body print in class Person {
  printf("Name:  %s\n", self->name);
  printf("Age:   %d\n", self->age);
};

method body get_age : integer in class Person {
  return self->age;
};

Let's define the get_salary method for the Tutor class. This method should compute a Tutor's salary by adding up the fees for all of the Tutor's students.

method body get_salary : real in class Tutor {
  o2 real total = 0.0; /* Precede local var declarations with "o2" */
  o2 tuple( name: string, fee: real ) curr_student;

  for ( curr_student in self->students ) {
    total += curr_student.fee;
  }

  self->salary = total;
  return total;
};

In the above example, every element of the "students" set has been iterated through by the 'for' loop.  In this case, each element is a tuple <name, fee> (note that you have to use the same tuple type as in the class definition). The current element being iterated is stored into the local variable, "curr_student". The "curr_student" variable is a tuple, which is basically like a struct. It's not a pointer (like "self"), so we use the . operator to access the tuple's data members (eg, curr_student.fee).

In the above example we could also use object disencapsulation to get the students of a "Tutor" object by using the "*" operator, which returns the value associated with an object:

curr_student = (*self)->strudents; /* assuming that curr_student is declared as a set of tuples */

O2 supports math (* + - / += etc) and boolean (&& || == != etc) expressions like C/C++. Note that the object comparison operator "==" implements the notion of "deep equality" and compares the whole structure of the objects (atomic, collection or tuple type attributes) and not only the atomic type attributes of them.

Next, for the class TATutor, we want to define the compute_salary method to add up the person's salaries as a TA and Tutor.  However, both classes (TA and Tutor) use the same name for their salary methods, "get_salary."  We can refer to each method by using the @ operator. The @ operator is like the scope operator (::) in C++.

method body compute_salary in class TATutor {
  self->total_salary = self->TA@get_salary + self->Tutor@get_salary;
};

For access data members with the same name, such as "salary" in the TA and Tutor classes, it's different.  We need to rename the data members in the TATutor class:

class TATutor inherit TA, Tutor
  rename  attribute salary from class TA as TA_salary,
  attribute salary from class Tutor as Tutor_salary
  public type tuple (
    total_salary: real
  )
  method public
    compute_salary
end;

Then we can implement compute_salary this way:

method body compute_salary in class TATutor {
  self->total_salary = self->TA_salary + self->Tutor_salary;
};

Building O2 Applications

Now that we have some classes and methods defined, we want to write some code that'll do something with these. You can write and execute O2C code in two ways.

The first one is to write and run some piece of code using "run body":

run body {
 o2 TA someTA = new TA;
 o2 real someSalary = 0.0;
 
 someTA->name = "Joe";
 someTA->age = 23;
 someTA->salary = 100.0;
 
 someSalary = someTA->get_salary;
 someTA->print;
 printf("$%.2f == $%.2f\n", someTA->get_salary, someSalary);
};

Remember that the data that we created in the above "run body" were local variables and that they will disappear once that piece of code finishes.

The second and more organized way is to create O2 applications that are collections of programs and variables. They make your code easier to write and run:

application PeopleAdder
  program  public add_Person,
    public add_TA,
    public add_Tutor,
    public add_TATutor
end;

You can then define the application's programs "add_Person" and so on.

/* This program modifies persistent data, namely the People set */
transaction body add_TATutor in application PeopleAdder {
  o2 TATutor newTutor = new TATutor;

  if (newTutor->edit) 
    People += set(newTutor); 
};

We added a "TATutor" object to the set "People" by adding (+=) a set containing the new "TATutor" object. If we want to remove an object from the "People" set we can use the "-=" operator. Also, if we want to update an object in the "People" set we can remove it from the set (-= ), update it using the local variable (Tutor) and then add it back to the "People" set (+= ).< /FONT>

Also note that the set "People" is a set of objects of type "Person". We can adda "TATutor" object to this set, because the class "TATutor" is a subclass of the "Person" class and so they are type compatible.< /FONT>

Because the above program modifies a persistent object ("People"), we must use the "transaction" keyword.  If you have a program that doesn't make any modifications, (eg, some program that displays information only), then use the keyword "program" instead of "transaction" at the beginning of the program definition.

Once you have your application and its programs defined, you can run a program of the application by using the "run" command:

run program add_TATutor in application PeopleAdder;

Deleting objects

You can delete unwanted schemas, bases ,classes, persistent variables and applications:

delete application PeopleAdder;
delete name People;
delete class Person;
delete base b;
delete schema s; /* You can't delete a schema if it has a base associated with it */

More Documentation

Change directory executing the following:

cd /software/nonrdist/o2_5.0/manuals

You can access the O2 Beginner’s Guide by opening the following file:

acroread o2cbegui.pdf

You can access the O2C Reference by opening the following file:

acroread o2cref.pdf