Functions
The create function statement defines a new user function stored in the database. Functions can be one of the following kinds:
Stored functions are stored in the sa.amos database as a table.
Derived functions are defined by a single query that returns the result of the a function call for given parameters.
Foreign functions are defined in an external programming language. Foreign functions can be defined in the programming languages C/C++ Ris12 or Java ER00.
Procedural functions are defined using procedural AmosQL statements that can have side effects changing the state of the database. Procedural functions make AmosQL computationally complete.
Overloaded functions have different implementations depending on the argument types in a function call.
Syntax:
create-function-stmt ::=
'create function' function-signature [function-body]
function-signature ::=
generic-function-name argument-spec '->' result-spec
argument-spec ::=
'(' [argument-declaration-commalist] ')'
argument-declaration ::=
type-spec [local-variable] [key-constraint]
key-constraint ::=
'key' | 'nonkey'
result-spec ::=
argument-spec | tuple-result-spec
tuple-result-spec ::=
['Bag of'] '(' argument-declaration-commalist ')'
function-body ::=
'as' query |
'stored' |
procedural-function-definition |
foreign-function-definition
Examples:
create function name(Person p) -> Charstring key
as stored;
create function born(Person) -> Integer
as stored;
create function children(Person p) -> Bag of Person c
as stored;
create function parents(Person c) -> Bag of Person p
as select p
from Person p
where c in children(p);
Function names are not case sensitive and are internally stored upper-cased.
Function signatures
The function signature defines the types of the arguments and results of a function.
Examples:
born(Person) -> Integer
name(Person p) -> Charstring key
children(Person p) -> Bag of Person c
All types used in the function signature must be previously defined.
The optional names of arguments and result parameters of a function signature must be unique.
Bag of
specifications on the result of a function sugnature declares
that the function returns a bag of values, rather than a single value.
Functions may return more than a single value as tuples specified by using the tuple-result-spec
notation.
For example:
create function marriages(Person p) -> Bag of (Person spouse, Integer year)
as stored;
has the signature marriages(Person p) -> Bag of (Person spouse, Integer year)
.
Tuple valued functions can be called in queries using the tuple-expression notation.
Function implementations
The function-body
notation specifies how values relate to arguments in a
function defintion.
Stored functions
A stored function is defined by the implementation as stored
.
Examples:
create function name(Person p) -> Charstring key
as stored;
create function born(Person) -> Integer
as stored;
A stored function is represented as a table in the database.
Notice that stored functions cannot have arguments declared Bag of
.
Derived functions
A derived function is defined by a single query.
Examples:
create function taxed_income(Person p) -> Number
as income(p) - taxes(p);
create function parents(Person c) -> Bag of Person p
as select p
from Person p
where c in children(p);
Functions with result type Boolean
implement predicates and return
true
when the condition is fulfilled.
Example:
create function child(Person p) -> Boolean
as select true where age(p)<18;
create function child(Person p) -> Boolean
as age(p) < 18;
Since the select statement returns a bag of values, derived functions
also often return Bag of
results. If you know that a function
returns a bag of values you should indicate that in the signature.
Example:
create function youngFriends(Person p)-> Bag of Person
as select f
from Person f
where age(f) < 18
and f in friends(p);
If you had written:
create function youngFriends(Person p) -> Person
as select f
from Person f
where age(f) < 18
and f in friends(p);
you would assume that youngFriends()
returns a single
value. However, this constraint is not enforced by the system so
if there are more that one youngFriends()
the system will treat the
result as a bag.
Variables declared in the result of a derived function need not be
declared again in the from
clause; their types are inferred from the
function signature.
Alternative definition of youngFriends()
:
create function youngFriends(Person p) -> Bag of Person f
as select f
where age(f) < 18
and f in friends(p);
Notice that the variable f
is bound to the elements of the bag,
not the entire bag itself.
Alternative definition of youngFriends()
:
create function youngFriends(Person p) -> Bag of (Person f)
as select f
where age(f) < 18
and f in friends(p);
Derived functions whose arguments are declared Bag of
define
aggregate functions. Aggregate functions do
not use Daplex semantics but compute values over their entire arguments bag.
Example:
create function myavg(Bag of Number x) -> Number
as sum(x)/count(x);
The following query computes the average age of Carl's grandparents:
select myavg(age(grandparents(q)))
from Person p
where name(q)="Carl";
When functions returning tuples are called in queries the results are bound by enclosing the function result within parentheses (..) through the tuple-expr syntax.
Example:
select name(s), y
from Person m, Person f, Person p
where (s,y) in marriages(p)
and name(p) = "Oscar";
Overloaded functions
Function names may be overloaded, i.e., functions having the same name may be defined differently for different argument types. This allows to define generic functions applicable on objects of several different argument types. Each specific implementation of an overloaded function is called a resolvent.
For example, the following two function definitions define the
resolvents of the overloaded function less()
:
create function less(Number i, Number j)->Boolean
as i < j;
create function less(Charstring s,Charstring t)->Boolean
as s < t;
Internally the system stores the resolvents under different internal
function names. The name of a resolvent is obtained by concatenating
the type names of its arguments with the name of the overloaded
function followed by the symbol ->
and the type of the result. The
two resolvents above will be given the internal resolvent names
NUMBER.NUMBER.LESS->BOOLEAN
and
CHARSTRING.CHARSTRING.LESS->BOOLEAN
.
function-name ::=
generic-function-name | resolvent-name
resolvent-name ::=
type-name-list '.' generic-function-name '->' type-name-list
type-name-list ::= type-name |
type-name '.' type-name-list
The query compiler resolves the correct resolvent to apply based on the types of the arguments; the type of the result is not considered. If there is an ambiguity, i.e. several resolvents qualify in a call, or if no resolvent qualify, an error will be generated by the query compiler.
When overloaded function names are encountered in function bodies, the system will use local variable declarations to choose the correct resolvent (early binding).
Example:
create function younger(Person p,Person q)->Boolean
as less(age(p),age(q));
will choose the resolvent NUMBER.NUMBER.LESS->BOOLEAN
, since age()
returns integers and the resolvent NUMBER.NUMBER.LESS->BOOLEAN
is
applicable to integers by inheritance. The other function resolvent
CHARSTRING.CHARSTRING.LESS->BOOLEAN
does not qualify since it cannot
have integer arguments.
For example, the function:
create function nameordered(Person p,Person q)->Boolean
as less(name(p),name(q));
will choose the resolvent CHARSTRING.CHARSTRING.LESS->BOOLEAN
since the
function name()
returns a string. In both cases the type
resolution (selection of resolvent) will be done at compile time.
Late binding
Sometimes it is not possible to determine the resolvent to choose based on its arguments, so the type resolution has to be done at run time. This is called late binding.
For example, suppose that managers are employees whose incomes are the sum of the income as a regular employee plus some manager bonus:
create type Employee under Person;
create type Manager under Employee;
create function mgrbonus(Manager)->Integer as stored;
create function income(Employee)->Integer as stored;
create function income(Manager m)->Integer i
as select income(e) + mgrbonus(m)
from Employee e
where e = m;
In the example the equality e = m
is used for selecting the salary of
the manager as a regular employee.
Now, suppose that we need a function that returns the gross incomes of
all persons in the database, i.e. we use MANAGER.INCOME->INTEGER
for
managers and EMPLOYEE.INCOME->INTEGER
for non-manager. Such a
function is defined as:
create function grossincomes() -> Integer i
as select income(p)
from Employee p;
Since income
is overloaded with resolvents EMPLOYEE.INCOME->INTEGER
and MANAGER.INCOME->INTEGER
and both qualify to apply to employees,
the resolution of income(p)
will be done at run time. To avoid the
overhead of late binding one may use casting, as explained in the next section.
Casting
The type of an expression can be explicitly defined using the casting statement:
casting ::=
'cast' (expr 'as' type-spec)
Example:
create function income(Manager m)->Integer i
as income(cast(m as Employee)) + mgrbonus(m);
Abstract functions
Sometimes there is a need to have a function defined for subtypes of a
common supertype, but the function should never be used for the
supertype itself. For example, one may have a common supertype Dog
with two subtypes Beagle
and Poodle
. One would like to have the
function bark()
defined for different kinds of dogs, but not for dogs in
general. In this case one defines the bark()
function for type Dog
as an
abstract function.
Example:
create type Dog;
create function name(Dog)->Charstring as stored;
create type Beagle under Dog;
create type Poodle under Dog;
create function bark(Dog d) -> Charstring as foreign 'abstract-function';
create function bark(Beagle d) -> Charstring;
create function bark(Poodle d) -> Charstring;
create Poodle(name,bark) instances ('Fido','yip yip');
create Beagle(name,bark) instances ('Snoopy','arf arf');
Now you can use bark()
as a function over dogs in general, but only
if the object is a subtype of Dog
:
select bark(d)
from dog d;
will return the bag:
"arf arf"
"yip yip"
An abstract function is defined by:
create function foo(...)->... as foreign 'abstract-function'.
If an abstract function is called it gives an informative error
message. For example, if one tries to call bark()
for an object of
type Dog
, an error message is printed.
Deleting functions
Functions are deleted with the delete function
statement.
Syntax:
delete-function-stmt ::=
'delete function' function-name
Examples:
delete function married;
delete function Person.name->Charstring;
Deleting a function also deletes all functions calling the deleted function.