Documentation home

Duro D/T Tutorial

Table of contents

Introduction

Duro D/T is an interpreter which supports accessing and updating DuroDBMS databases. Being computationally complete, it can be used to write database applications. The language is very close to the Tutorial D language proposed by C. J. Date and Hugh Darwen in their book The Third Manifesto (TTM).

Consult The Third Manifesto website for more information on TTM and Tutorial D.

Invoking the interpreter

To invoke the interpreter, type durodt:

(Assuming the directory containing the durodt binary has been added to the system path)

durodt
Duro D/T library version 1.7
Implicit transactions enabled.
no db>

Duro D/T displays a prompt with the database currently in use. Since no database is selected in the example shown above, the prompt is "no db".

To exit the interpreter, type the end-of-file character (Control-Z on Windows, Control-D on POSIX systems) or type "os.exit();". (Do not forget the parentheses and the semicolon)

For more information about invoking Duro D/T see DuroDBMS utilities.

Declaring variables

In Duro D/T, variables are typed, as required by TTM. They must be declared before they can be used.

Example:

no db> var i integer;
no db>

This creates a variable named i. Note that Duro D/T is case insensitive, so there is no diffence between "var i integer;" and "VAR I INTEGER;". More precisely, keywords are case insensitive whereas identifiers are automatically converted to lowercase. This tutorial shows example code in lowercase.

When declaring a variable, an initial value can be specified:

no db> var j integer init 2;
no db>

If an initial value is provided, the type can be omitted. So the above can also be written as follows:

no db> var j init 2;
no db>

If an initial value is not specified, the variable is automatically initialized to a default value, which is zero for numeric types, the empty string for the type string and FALSE for boolean.

A variable can also be declared to have the same type as some other variable. Example:

no db> var k same_type_as(i);
no db>

Automatic conversion to lowercase does not take place if backtick-quoting is used:

no db> var `MyVar` int;
no db>

Backtick-quoting also has to be used if an identifier would conflict with a keyword.

Variables can be explicity destroyed. This is usually not necessary for transient variables, but is supported for all user-declared variables:

no db> drop var k;
no db>

Constants

Symbolic constants can be created using CONST:

no db> const pi 3.141592653;
no db>

Assignment

Once a variable has been declared, a value can be assigned to it. For example:

no db> i := 5;
1 element affected.
no db>

Duro D/T supports multiple assignment, i.e. more than one assignment in one statement. A multiple assignment behaves as if the individual assignments were executed in parallel.

For example, to exchange the values of two variables, one can write:

no db> i := j, j := i;
2 elements affected.
no db>

Built-in types

DuroDBMS supports the following built-in basic scalar types:

Examples:

no db> var b bool init true;
no db> var i int init 1;
no db> var f float init 1.0;
no db> var s string init 'Foo';
no db> var bin binary init X'466f6f';

There are other built-in types used for I/O, date/time and exception handling. These are discussed in the respective sections.

For non-scalar types, see the chapters on tuples, tables, and arrays, respectively.

DuroDBMS supports the usual arithmetical operators. Example:

no db> io.put_line(cast_as_string(2 + 3 * 4));
14
no db>

cast_as_<type> is the type conversion operator. Here it is used to convert the integer result to a string. In Duro D/T, type conversion is always explicit.

For details on the built-in operator io.put_line, see the chapter on I/O below.

NaN is supported as a float value:

no db> f := NaN;
1 element affected.
no db>

NaN is equal to itself. In this regard, it does not follow the IEEE 754 standard because a value not being equal to itself would violate RM prescription 8.

Comments

Duro D/T supports end-of-line comments and block comments.

var i int; -- This is an end-of-line comment.

/* This is a
block
comment. */

End-of-line comments and block comments which span several lines are not supported in interactive mode.

Lines starting with #! are ignored to allow for Unix interpreter files.

Control statements

IF

Example:

no db> var s string;
no db> io.put_line('Please enter a number:'); io.get_line(s);
Please enter a number:
2
no db> var n init cast_as_integer(s);
no db> if n < 0 then
no db>     io.put_line('Negative');
no db>     io.put_line('Sign is -');
no db> else
no db>     io.put_line('Positive');
no db>     io.put_line('Sign is +');
no db> end if;
Positive
Sign is +
no db>

In Duro D/T, BEGIN .. END is not required since there can be several statements after THEN. But the following form, which more strictly adheres to TTM, is supported too:

no db> var s string;
no db> io.put_line('Please enter a number:'); io.get_line(s);
Please enter a number:
-2
no db> var n init cast_as_integer(s);
no db> if n < 0 then
no db>     begin;
no db>         io.put_line('Negative');
no db>         io.put_line('Sign is -');
no db>     end;
no db> else
no db>     begin;
no db>         io.put_line('Positive');
no db>         io.put_line('Sign is +');
no db>     end;
no db> end if;
Negative
Sign is -
no db>

FOR

Example:

no db> var i integer;
no db> for i := 1 to 5;
no db>     io.put(i);
no db>     io.put_line('');
no db> end for;
1
2
3
4
5
no db>

DO may be used instead of FOR.

WHILE

Example:

var s string;
var n integer;

io.get_line(s);
n := cast_as_integer(s);
while n < 0;
    io.put_line('This number is not positive. Please enter again:');
    io.get_line(s);
    n := cast_as_integer(s);
end while;

This code reads one line of input until you enter a positive number. (Or a string which is not a number which will result in an invalid_argument_error)

It is possible to exit a loop using the LEAVE statement. Example:

no db> var s string;
no db> while true;
no db>     io.put('type "exit" to exit: ');
no db>     io.get_line(s);
no db>     if s = 'exit' then
no db>         leave;
no db>     end if;
no db> end while;
type "exit" to exit: x
type "exit" to exit: exit
no db> 

LEAVE with a statement name (label) is also supported. See TTM, chapter 5 for details.

CASE

Example:

no db> var s string;
no db> var n integer;
no db> io.put_line('Please enter a number:'); io.get_line(s);
Please enter a number:
0
no db> n := cast_as_integer(s);
no db> case
no db>     when n > 0 then
no db>         io.put_line('Positive');
no db>     when n < 0 then
no db>         io.put_line('Negative');
no db>     else
no db>         io.put_line('Zero');
no db> end case;
Zero
no db>

Tuples

Tuple variables can be declared as follows:

no db> var tp tuple { i integer, s string };
no db>

TUP can be used as a synonym for TUPLE. Like scalar variables, tuple variables can be explicitly initialized when they are declared:

no db> var tp tup { i integer, s string } init tup { i 1, s 'One' };
no db>
Or shorter:
no db> var tp init tup { i 1, s 'One' };
no db>

Tuple assignment:

no db> tp := tup { i 2, s 'Two' };
1 element affected.
Note that as required by TTM proscription 1 there is no attribute ordering in Duro D/T, so the statement above is perfectly equivalent to the following:
no db> tp := tup { s 'Two', i 2 };
1 element affected.

Tuple update:

no db> update tp { i := 3 };
1 element affected.
no db> update tp { i := 4, s := 'Four' };
1 element affected.

Accessing tuple attributes:

no db> io.put_line(tp.s);
Four
no db>

Alternative form:

no db> io.put_line(s from tp);
Four
no db>

Databases, database environments, and transactions

Database environments are the places where databases and tables are physically stored. Therefore a database environment must exist before a database can be created. In Duro D/T, a database environment can be created using the built-in update operator create_env():

no db> create_env('dbenv');
no db>

The built-in update operator connect() can be used to connect to an existing database environment. connect() takes one argument of type string which specifies the database environment:

no db> connect('dbenv');
no db>

By default, a DuroDBMS database environment is implemented using a Berkeley DB database environment. The Berkeley DB command line utilities can be used for tasks like backup, recovery etc.

If DuroDBMS has been built with PostgreSQL support, a PostgreSQL database can be used as a database environment by passing a PostgreSQL URI to connect(), e.g.:

no db> connect('postgresql:///mydb');
no db>

Creating a PostgreSQL database using Duro D/T is not supported. This has to be done using one of the PostgreSQL utilities, e.g. createdb.

If DuroDBMS has been built with FoundationDB support, a FoundationDB database can be used as a database environment by passing a FoundationDB cluster file path preceded by 'foundationdb://'. E.g.:

no db> connect('foundationdb://');
no db>

After a database environment was created and/or connected to, a database can be created using the built-in update operator create_db().

no db> create_db('D');
no db>

The variable current_db contains the database currently in use. A database can be selected by assigning the variable:

no db> current_db := 'D';
1 element affected.
D>

After current_db has been set, the prompt shows the name of the database selected and a transaction can be started using BEGIN TRANSACTION. Instead of BEGIN TRANSACTION, BEGIN TX can be used:

D> begin tx;
Transaction started.
D>

Transaction commit:

D> commit;
Transaction committed.
D>

Transaction rollback:

D> begin tx;
Transaction started.
D> rollback;
Transaction rolled back.
D>

If implicit transactions are enabled, no transaction is running and the value of current_db is not the empty string, the execution of each statement will be preceded by an implicit begin transaction and followed by an implicit commit (if the statement executed successfully) or rollback (otherwise).

In interactive mode, implicit transactions are enabled by default. Implicit transactions can be enabled and disabled by setting the variable implicit_tx to TRUE or FALSE, respectively.

The following examples assume that implicit transactions are enabled.

Tables

As required by TTM, tables are variables in DuroDBMS, thus a table is created by declaring a variable.

Real tables

Creating a real table:

D> var items real relation { itemno int, name string, price float } key { itemno };
Table items created.
D>

REL can be used as a synonym for RELATION.

It is possible to specify default values when creating a table. Example:

D> var items2 real rel { itemno int, name string, price float } key { itemno } default { price 0.0 };
Table items2 created.
D> insert items2 tup { itemno 1, name 'Sunglasses' };
1 element affected.

serial() can be used as a default value. In this case, the attribute value will be obtained automatically.

Example:

D> var items3 real rel { itemno int, name string, price float } key { itemno } default { itemno serial() };
Table r3 created.
D> insert items3 tup { name 'Bag', price 19.95 };
1 element affected.
D> insert items3 tup { name 'Hat', price 25.0 };
1 element affected.
D> io.put(items3); io.put_line('');
RELATION {TUPLE {itemno 2, name 'Hat', price 25.0}, TUPLE {itemno 1, name 'Bag', price 19.95}}
D>

Private tables

Private tables are transient, they do not belong to any database and do not require a transaction to create or access them.

Creating a private table:

D> var p private rel { no int, name string } key { no };
Local table p created.
D>

Virtual tables (aka views)

Creating a virtual table:

D> var cheap_item_names virtual items where price < 5.0 { name };
Table cheap_item_names created.
D>

This creates a view which contains the names of all items with a price lower than 5.

Public tables

DuroDBMS supports public tables as a means to provide logical data independence, separating the application from the database.

A public table can be defined as follows:

D> var app_items public relation { itemno int, name string, price float } key { itemno };
Public table app_items created.
D> 

Before a public table can be used, a mapping between it and and one or more database tables must be defined. This can be done using the MAP operator.

A simple example (using the table items defined above):

D> map app_items items2;
D>

Insert, update, delete

Inserting a single tuple:

D> insert items tup { itemno 1, name 'Wallet', price 15.0 };
1 element affected.
D>

Inserting several tuples:

D> insert items rel { tup { itemno 2, name 'T-Shirt', price 23.99 },
                      tup { itemno 3, name 'Cap', price 18.99 } };
2 elements affected.
D>

Updating a table:

D> update items where itemno = 3 { itemno := 4, name := 'Baseball cap' };
1 element affected.
D>

Deleting tuples from a table:

D> delete items where itemno = 4;
1 element affected.
D> delete items tup { itemno 2, name 'T-Shirt', price 23.99 };
1 element affected.
D>

Tables can be destroyed just as other variables using DROP VAR:

D> drop var cheap_item_names;
Table cheap_item_names dropped.
D> drop var items;
Table items dropped.
D>

Renaming a table

A table can be renamed using RENAME VAR:

D> rename var items3 as itemsn;
Table items3 renamed.
D>

This works only for persistent tables.

Database constraints

Duro D/T supports database constraints.

A simple example

Let's say we have a table which we want to contain no more than 2 tuples. This can be realized as follows:

D> var t real rel { n int, s string } key { n };
Table t created.
D> constraint cr count(t) <= 2;
Constraint cr created.
D> insert t tup { n 1, s 'one' };
1 element affected.
D> insert t tup { n 2, s 'two' };
1 element affected.
D> insert t tup { n 3, s 'three' };
predicate_violation_error: cr
D> 

A more complex example

Imagine there is one table for customers and one for orders:

D> var customers real rel { customer_id int, name string } key { customer_id };
Table customers created.
D> var orders real rel { order_id int, customer_id int } key { order_id };
Table orders created.
D> 

If we only want to allow orders with a corresponding customer, this can be achieved using a database constraint as follows:

D> constraint orders_customers orders { customer_id } subset_of customers { customer_id };
Constraint orders_customers created.
D> 

If we now want to insert an order, DuroDBMS will check if there is a corresponding entry in the customer table:

D> insert orders tup { order_id 1, customer_id 1 };
predicate_violation_error: orders_customers
D> insert customers tup { customer_id 1, name 'John Smith' };
1 element affected.
D> insert orders tup { order_id 1, customer_id 1 };
1 element affected.
D> 

DuroDBMS performs constraint checking before a statement is executed. To achieve this, DuroDBMS generates an expression which will return TRUE if the statement will not violate the expression and FALSE if it will. Duro tries to optimize this expression before evaluating.

An EXPLAIN statement can be used to obtain the expression which is evaluated in order to check a constraint. Example:

D> explain insert orders tup { order_id 1, customer_id 1 };
check orders_customers: is_empty((RELATION {} union ((RELATION {TUPLE {customer_id 1, order_id 1}}
{ customer_id }) minus (customers INDEX customers$0 { customer_id }))) {  })
D> 

The expression is a little complicated, but one can see that the reference to table orders has been eliminated. The expression simply checks if the customer_id of the tuple to be inserted already appears in customers.

A constraint can be deleted using DROP CONSTRAINT:

D> drop constraint orders_customers;
Constraint orders_customers dropped.
D>

Transition constraints

A constraint may refer to the value of a table as it was prior to the update by adding a ' (apostrophe) to the name of the table.

For example, if we have a table defined as follows:

D> var rs real rel { id int, status int } key { id };
Table rs created.
D>

The following constraint says that the value of the attribute status may never decrease:

D> constraint trc is_empty(((rs' rename { status as status' }) join rs) where status' > status);
Constraint trc created.
D> insert rs tup { id 1, status 0 };
1 element affected.
D> update rs where id = 1 { status := 1 };
1 element affected.
D> update rs where id = 1 { status := 0 };
predicate_violation_error: trc

Arrays

Example: Printing all tuples of table r in an unspecified order:

D> var a array tup same_heading_as(r);
D> load a from r order();
D> var i integer;
D> for i:= 0 to length(a) - 1;
D>   io.put(a[i]); io.put_line('');
D> end for;

FOR .. IN

Duro D/T supports a second FOR statement which iterates over all tuple values of a table. The example below prints all tuples of table r in an unspecified order using FOR instead of using LOAD:

D> var tp tup same_heading_as(r);
D> for tp in r order();
D>   io.put(tp); io.put_line('');
D> end for;

I/O

Duro D/T provides a number of I/O operators.

io.put_line

io.put_line is an update operator that takes one argument of type string and prints it on standard output, followed by a newline.

no db> io.put_line('hello');
hello
no db>

Note that in Duro D/T, the keyword CALL in update operator invocations is optional. The following is also valid:

no db> call io.put_line('hello');
hello
no db>

io.put

io.put works like io.put_line, except that its output is not followed by a newline. Unlike io.put_line, it is defined for all built-in basic data types and also for tuple and relation arguments:

no db> io.put('2 + 2 = '); io.put(2 + 2); io.put_line('');
2 + 2 = 4
no db> io.put(true); io.put_line('');
TRUE
no db> io.put(tup {a 1, b 'bee'}); io.put_line('');
TUPLE { a 1, b 'bee' }
no db> io.put(rel {tup {I 1, C 'foo'}, tup {I 2, C 'bar'}}); io.put_line('');
RELATION { TUPLE { i 1, c 'foo' }, TUPLE { i 2, c 'bar' }}
no db>

io.get_line

io.get_line is defined as follows:

PACKAGE io;

OPERATOR get_line(line string) UPDATES { line };

END PACKAGE;

io.get_line reads one line from standard input and stores it in line, without the trailing newline.

File I/O is also supported. See the list of I/O operators.

The following example code reads lines from standard input and writes them to standard output in sorted order:

var lines private relation { lineno int, line string } key { lineno };
var l string;

-- Read input lines
var lno init 0;
while true;
    io.get_line(l);
    -- Leave loop on EOF
    if io.eof() then
        leave;
    end if;
    insert lines tup { lineno lno, line l };
    lno := lno + 1;
end while;

-- Write lines in sorted order
var lt tup same_heading_as(lines);
for lt in lines order(line asc);
    io.put_line(lt.line);
end for;

Formatted output

The built-in read-only operator format provides a way of performing formatted output.

Example:

no db> var n init 2;
no db> io.put_line(format('The value of n is %d', n));
The value of n is 2
no db>

User-defined operators

Defining a read-only operator:

D> operator incr(i integer) returns integer;
D> return i + 1;
D> end operator;
Read-only operator incr created.
D> io.put(incr(1)); io.put_line('');
2
D>

Defining an update operator:

D> operator inc(i integer) updates { i };
D> i := i + 1;
D> end operator;
Operator inc created.
D> var n init 1;
D> inc(n);
D> io.put(n); io.put_line('');
2
D>

Dropping an operator:

D> drop operator incr;
Operator incr dropped.
D>

Overloading operators is permitted. Note that if more than one operator is created with the same name, it is not considered a different operator but a different version of the same operator.

DROP OPERATOR will delete all versions of the read-only operator with the name given, if at least one version with that name exists. Otherwise, all versions of the update operator with the that name are deleted.

An operator can be created with a version name. Example:

D> operator incr(i integer) returns integer version i;
D> return i + 1;
D> end operator;
Read-only operator incr created.

An operator version can be dropped as follows:

D> drop operator incr version i;
Operator incr dropped.

Date and time

DuroDBMS provides a type datetime which is defined as follows:

TYPE datetime
POSSREP {
    year integer,
    month integer,
    day integer,
    hour integer,
    minute integer,
    second integer
}; 

The read-only operators now() and now_utc() return the current time. now() returns the current time as a local time whereas now_utc() returns the UTC time.

Example:

no db> var d datetime;
no db> d := now();
1 element affected.
no db> io.put_line(cast_as_string(d));
2015-05-18T14:15:29
no db> d := now_utc();
1 element affected.
no db> io.put_line(cast_as_string(d));
2015-05-18T12:15:51
no db> 

User-defined types

Imagine we have employee numbers which start with the character 'E' followed by one or more digits.

We can define a type for this purpose as follows:

D> type empno possrep (no string)
D> constraint no regex_like '^E[0-9]+$' or no = ''
D> init empno('');
Type empno defined.
D> 

We also allow the empty string if there is no employee number to store. It may be a good idea to allow only values of a certain (fixed) length; this is omitted here to keep the example simple.

Before the type can be used, it must first be implemented. This can be done using IMPLEMENT TYPE:

D> implement type empno; end implement;
Type empno implemented.

After a type has been defined and implemented, it can be used to declare and assign variables. Example:

D> var empno empno;
D> empno := empno('1234');
type_constraint_violation_error: empno
D> empno := empno('A1234');
type_constraint_violation_error: empno
D> empno := empno('E1234');
1 element affected.
D> io.put_line(empno.no);
E1234
D> 

The no property can also be accessed using a the_ operator:

D> io.put_line(the_no(empno));
E1234
D> 

Things are getting a little more complex if a user-defined type has more than one possible representation. In this case, an actual representation must be specified together with selector, getter, and setter operators which provide the mapping between the possible representations and the actual representation. These operators are accessing the actual representation.

Note that the type constraint is specified for the entire type and not for the individual possreps, as opposed to TTM.

The example code below shows how the type POINT from TTM, chapter 3, can be implemented using Duro D/T.

type point
    possrep cartesian (x float, y float)
    possrep polar (r float, theta float)
    init cartesian(0.0, 0.0);

implement type point as tuple { x float, y float };

    operator cartesian(x float, y float) returns point;
        return tuple { x x, y y };
    end operator;

    operator get_x(p point) returns float;
        return p.x;
    end operator;

    operator get_y(p point) returns float;
        return p.y;
    end operator;

    operator set_x(p point, x_ float) updates { p };
        update p { x := x_ };
    end operator;

    operator set_y(p point, y_ float) updates { p };
        update p { y := y_ };
    end operator;

    operator polar(r float, theta float) returns point;
        return tuple { x r * cos(theta), y r * sin(theta) };
    end operator;

    operator get_r(p point) returns float;
        return sqrt(p.x * p.x + p.y * p.y);
    end operator;

    operator set_r(p point, r float) updates { p };
        var theta init atan2(p.y, p.x);

        update p { x := cos(theta) * r, y := sin(theta) * r };
    end operator;

    operator set_theta(p point, theta float) updates { p };
        var r init sqrt(p.x * p.x + p.y * p.y);

        update p { x := cos(theta) * r, y := sin(theta) * r };
    end operator;

    operator get_theta(p point) returns float;
        return atan2(p.y, p.x);
    end operator;

end implement;

Usage examples:

D> var p point;
D> p := cartesian(1.0, 1.0);
1 element affected.
D> io.put(p.x); io.put_line('');
1.0
D> io.put(p.y); io.put_line('');
1.0
D> io.put(p.r); io.put_line('');
1.4142135624
D> io.put(p.theta); io.put_line('');
0.7853981634
D> 

Subtyping

Since version 1.2, Duro D/T supports subtyping, however, suptyping support is limited in that only dummy types (types without possible representations) can be supertypes.

The following example code creates a parent type and two subtypes:

D> type shape union;
Type shape defined.
D> type circle is shape possrep (radius int) init circle(0);
Type circle defined.
D> type rectangle is shape possrep (width int, height int) init rectangle(0,0);
Type rectangle defined.
D> implement type circle;
D> end implement;
Type circle implemented.
D> implement type rectangle;
D> end implement;
Type rectangle implemented.
D> 

Note that the parent type must be declared as a union type and that it must not be implemented.

Duro D/T supports type testing and TREAT (see TTM, IM prescription 14).

Example:

D> var s shape init circle(1);
D> io.put(is_circle(s)); io.put_line('');
TRUE
D> io.put(treat_as_circle(s).radius); io.put_line('');
1
D> 

Subtyping with read-only operators

Example:

D> operator area(s shape) returns integer;
D> end operator;
Read-only operator area created.
D> operator area(r rectangle) returns integer;
D> return r.width * r.height;
D> end operator;
Read-only operator area created.
D> operator area(c circle) returns integer;
D> return c.radius * 710 / 113;
D> end operator;
Read-only operator area created.
D> var s2 shape init rectangle(3,4);
D> io.put(area(s2)); io.put_line('');
12
D> s2 := circle(5);
1 element affected.
D> io.put(area(s2)); io.put_line('');
31

Subtyping with update operators

Example:

D> operator scale(s shape, r float) updates { s };
D> end operator;
Update operator scale created.
D> operator scale(c circle, r float) updates { c };
D> c.radius := cast_as_integer(cast_as_float(c.radius) * r);
D> end operator;
Update operator scale created.
D> operator scale(rec rectangle, r float) updates { rec };
D> rec.width := cast_as_integer(cast_as_float(rec.width) * r);
D> rec.height := cast_as_integer(cast_as_float(rec.height) * r);
D> end operator;
Update operator scale created.
D> var s3 shape init circle(100);
D> scale(s3, 1.5);
D> io.put(treat_as_circle(s3).radius); io.put_line('');
150

Exception handling

By default, if an error occurs, execution stops.

In Duro D/T, it is possible to handle errors and to continue execution. It is also possible to generate errors from user code.

RAISE

The RAISE statement generates an error. It can take any argument and will use it as an error value:

D> raise 2;
integer
D>

If an error is not handled, execution stops and the error type is printed to standard output. This is why we see "integer" appear.

Raising a system-provided error:

D> raise name_error('test');
name_error: test
D>

If the error type has a msg property, its value is printed together with the type. See the built-in types for a list of system-provided error types.

TRY .. CATCH

TRY .. CATCH can be used to catch errors that may be raised during execution. A TRY block must contain one or more CATCH statements.

The following code illustrates catching errors using TRY .. CATCH:

try

/* Perform operations that could throw errors */

...

catch err invalid_argument_error;

  /* Handle invalid_argument_error */

...

catch err;

  /* Handle remaining errors */

...

end try;

Note that there must at least one statement between CATCH and END TRY.

Packages

Operators, types, and public tables can be organized in packages.

A package consists of a definition and an (optional) implementation part. Database tables cannot be part of a package.

In practice, it may be a good idea to clearly distinguish between application packages containing application code and public tables on the one hand and 'base packages' containing types and operators used in databases on the other, avoiding a mutual dependency between databases and packages.

Example:

package emp;

    type empno possrep (no int) init emp.empno(0);

end package;

implement package emp;

    implement type empno;
    end implement;

end implement;

var employees real relation { empno emp.empno, name string } key { empno };

package empapp;

    var employees public relation { empno emp.empno, name string } key { empno };

    operator print_employees() updates {};
        var tp tuple same_heading_as(empapp.employees);

        for tp in empapp.employees order (name asc);
            io.put(tp.empno.no);
            io.put(' ');
            io.put_line(tp.name);
        end for;
    end operator;

end package;

implement package empapp;

    map employees employees;

end implement;

Operators, types, and public tables contained in a package can be dropped by surrounding the respective DROP statements by PACKAGE .. END PACKAGE;

Example:

package empapp;

    drop operator print_employees;

end package;

DROP PACKAGE deletes a package by deleting all operators, types, and public tables the package contains.

Example:

drop package empapp;

Performance

Duro D/T features an EXPLAIN statement which displays how a relational expression is executed.

Example:

D> var r real relation { no int, name string } key { no };
Table r created.
D> var i int;
D> for i := 1 to 10; insert r tup { no i, name cast_as_string(i) }; end for;
1 element affected.
1 element affected.
1 element affected.
1 element affected.
1 element affected.
1 element affected.
1 element affected.
1 element affected.
1 element affected.
1 element affected.
D> explain r where no = 1 order();
(r INDEX r$0) where (no = 1)

(This EXPLAIN statement is not to be confused with the EXPLAIN which displays the constraint checking performed for a statement)

The output of EXPLAIN shows that Duro D/T uses the index r$0 to evaluate the expression. This is the unique index which was created to ensure that n is a key attribute of r.

Now let's try other WHERE expressions:

D> explain r where no > 1 order();
(r) where (no > 1)
D> explain r where name = 'one' order();
(r) where (name = 'one')

For neither of these an index can be used. Not for the first, because the system-generated index on n is a hash index which supports only equality, and not for the second because there is no index on attribute s. This means that DuroDBMS will scan the entire table to evaluate the expression which is quite inefficient for large tables.

We can create an index on s using the INDEX statement:

D> index r_name r (name);
Index r_name created.
Now we can use the index:
D> explain r where name = 'one' order();
(r INDEX r_name) where (name = 'one')
As we can see in the output of the EXPLAIN statement, DuroDBMS now uses the index r_name to evaluate the expression. An index can be destroyed using DROP INDEX:
D> drop index r_name;
Index r_name dropped.

INDEX is supported for physically stored database tables. Currently real tables are physically stored and virtual tables are not, but this may change in future versions of DuroDBMS.

Note that INDEX and DROP INDEX are low-level operators; access to them may be restricted in future versions of DuroDBMS.