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.
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.
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>
Symbolic constants can be created using CONST:
no db> const pi 3.141592653; no db>
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>
DuroDBMS supports the following built-in basic scalar types:
boolean
(synonym bool
)integer
(synonym int
)float
(synonyms rational
, rat
)string
(synonym char
)binary
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.
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.
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>
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.
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.
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>
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>
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.
As required by TTM, tables are variables in DuroDBMS, thus a table is created by declaring a variable.
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 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>
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.
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>
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>
A table can be renamed using RENAME VAR:
D> rename var items3 as itemsn; Table items3 renamed. D>
This works only for persistent tables.
Duro D/T supports database constraints.
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>
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>
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
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;
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;
Duro D/T provides a number of I/O operators.
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
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
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;
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>
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.
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>
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>
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>
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
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
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.
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 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.
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;
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.