Functions
The create function statement defines a new function stored in the
database. Function names are not case sensitive and are
internally stored upper-cased.
Example:
create function absdiff(Number x, Number y) -> Number
as abs(x-y)
absdiff(3,5)
The result of a create function call is an object of type
Function.
Function signatures
A function consists of two parts, the signature and the body:
The function signature is a human readable string containing the
types and names of the arguments and result of a function. For
example, absdiff(Number x, Number y)->Number.
You can call the system function signature(Charstring fn)->Bag of
Charstring to get the signatures of all functions named fn.
Examples:
signature("absdiff")
signature("mod")
signature("range")
There is only one function named mod, while the function range is
an
overloaded
function having three different
resolvents
with different definitions depending on their argument types.
The signature of a Boolean function, i.e. a function whose result
type is `Boolean, has no result type.
Example:
signature("=")
You can also specify the function's name in signature(fn) as a
string pattern.
Examples:
signature("atan%")
signature("atan_")
Function bodies
The function body specifies how to compute the result of a
function for given argument values. For example, the body of the
function plus(Number x,Number y)->Number r specifies how to compute
the result r by adding the arguments x and y.
Example:
create function add1(Integer i) -> Integer
as i + 1
The signature of add1 is add1(Integer i)->Integer and the body is
i+1.
In many cases the system can infer the inverse of a function call
in a query for given results. For example, the body of plus(Number
x,Number y)->Number r also specifies how to compute y when x and
r are known; the system knows that - is the inverse of +.
Example: The following
set query calls the
inverse of +, i.e. - by analyzing the body of add1 when i is
unknwówn in the expression add1(i)=5.
select i
from Integer i
where add1(i) = 5
The function body is normally non-procedural (or declarative), i.e. a function only computes result values for given arguments (and vice versa when possible) and does not have any side effects. The exception is procedural functions defined through OSQL statements having side effects.
In general, depending on their bodies, functions can be one of the following kinds:
Stored functions are tabulated functions whose values for given parameters can be updated.
Derived functions are defined by a single expression or query that returns the result of a function call for given parameters.
Procedural functions are defined using procedural OSQL statements that can have side effects changing the state of the local database or the environment. Procedural functions make OSQL computationally complete.
Foreign functions are defined in an external programming language. Foreign functions can be defined in the programming languages Lisp, Java, Python, C, or C++.
Overloaded functions have different implementations depending on the argument types in a function call.
Stored functions
Stored functions are functions whose mappings from arguments to results, their extents, are stored in the local database of a peer. They are an alternative to SQL tables to represent simple properties as mappings from arguments to values.
A stored function is defined by the function body as stored.
Example:
create function income(Charstring person) -> Real
as stored
You can update the value of a stored function for given arguments
using a variant of the set statement. You are then updating the
extent of the function and can query it as a functional expression.
Example:
set income('Tore') = 1000;
set income('Ulla') = 2000
income('Tore')
The set statement allows to change stored function values.
Example:
set income('Tore') = 1500
income('Tore')
The result of a stored function may be a single value as for income.
It may also be a bag.
Example:
create function parents(Charstring child) -> Bag of Charstring
as stored
set parents('Tore') = (values 'Ulla', 'Olof');
set parents('Ulla') = 'Karl'
parents('Tore')
You can add elements to the result of functions returning bags
with the add statement.
Example:
add parents('Ulla') = 'Marie'
parents('Ulla')
You can remove all stored values for given arguments by setting
them to null.
Example:
set parents('Tore') = null
parents('Tore')
The function extent(Function f)->Bag of Vector returns rows in
function f as vectors.
Example:
```osql wasm extent(thefunction('parents')) ```Here the function thefunction(Charstring fn)->Function returns the
function named fn. It fails if there is no such function.
Function calls
In a function call, the types of the actual parameters must be the same as, or sub-types of, the types of the corresponding formal parameters.
Example: This function call raises an exception:
range('10')
If a function is applied on the result of a function returning a bag of values, the outer function is applied on each element of that bag, the bag is flattened. This is called Daplex semantics.
Example: The following query
returns the grandparents of 'Tore'.
parents(parents('Tore'))
In the query the function parents(p) returns a bag of two strings, for
which the function parents() is applied again on each element.
Infix operators
The built-in function plus() has a corresponding infix operator
+ where x+y is equivalent to plus(x,y). Analogously there are infix
operators - for minus(), * for times(), and / for div(), etc.
Example:
(sin(3.14)+sin(6.28)) * 0.5
is equivalent to:
times(plus(sin(3.14),sin(6.28)),0.5)
The infix operator || concatenates strings.
Example:
upper("Hello World") || '!'
For legacy reasons the infix + operator can be used for string
concatenation as well, but this is discouraged as ambiguities may
occur since + is also defined for many other data types than
strings.
The in operator
If a function returns a
collection
the elements of the collection can extracted by using the infix in
operator.
Example: The following query returns the grandparents of 'Tore'
without using Daplex
semantics.
select gp
from Charstring gp, Charstring p
where p in parents('Tore')
and gp in parents(p)
The in operator actually converts the elements of the collection
into a bag.
Casting
The cast operator can be used for explicitly converting between
different types.
Example:
cast(1.1 as Integer)
Tuple valued functions
A function my return not only singleton values, but also tuples of two or more values.
Example:
divide(5,3)
In this case the function divide(Integer i,Integer j)->(Integer
q,Integer r) is a tuple valued function that returns both the
quotient q and the remainder r of dividing i with j. The
function returns the result tuple (q,r) for given argument
tuple (i,j).
To bind the results of tuple valued functions use tuples.
Example: The following query uses the tuple (d,rem) to compute the
remainder while ignoring the quotient:
select rem
from Integer d, Integer rem
where (d,rem) = divide(5,3)
Tuples can also be used to assign separate variables to the result of a function returning a tuple.
Example:
set (:d,:r)=divide(5,3)
:d + :r
Derived functions
A derived function is defined by a single expression or query.
Examples:
create function degrees(Real radians)->Real
as radians*180/pi()
degrees(pi())
Derived functions usually return bags of values.
Examples:
create function square_roots(Real x)->Bag of Real
as select (values r,-r)
from Real r
where r = sqrt(x)
square_roots(2)
The query in a derived function may reference both local variables and table columns.
Example: Let's create a new table Person.
drop table Person;
create table Person(
id Integer,
name Charstring,
income Real,
unique(id));
insert into Person values
(1,'Tore',1000.0),
(2,'Kalle',2000.0)
The derived function rich_persons(l) selects the names of
persons earning more than l from table Person.
create function rich_persons(Real l) -> Bag of Charstring
as select name
from Person
where income > l
rich_persons(1000)
To disambiguate name collisions, use SQL's dot notation to access table column names in queries.
Example:
create function person_name(Integer id) -> Charstring
as select name
from Person p
where p.id = id
person_name(1)
Derived Boolean functions return true when a
condition is fulfilled.
Example:
create function positive(Number n) -> Boolean
as n > 0
positive(2)
Aggregate functions
Aggregate functions compute a single result from the elements in a collection not using Daplex sematics.
Examples:
avg(values 1,2,3)
avg([1,2,3])
avg(array([1,2,3]))
The argument of an aggregate function is a collection and the result is a single value.
Example:
signature("avg")
You can define your own aggregate function over bags by declaring its argument
Bag of ...
Example:
create function myavg(Bag of Number b) -> Number
as sum(b)/count(b)
myavg(values 1,2,3)
Aggregate functions over bags can be used in group by.
Overloaded functions
Functions may be overloaded, i.e. functions having the same generic name may be defined differently for different argument types. This allows to define functions applicable on objects of several different argument types. Each specific implementation of an overloaded function is called a resolvent.
Example: The following two function definitions create two
resolvents of the overloaded
Boolean function
less():
create function less(Number i, Number j)->Boolean
as i < j;
create function less(Charstring s,Charstring t)->Boolean
as s < t
You can inspect what resolvents are defined for a given generic
function name g by calling signature(g).
Example:
signature('less')
You can also retrieve the object representing the resolvents for a
given generic function by calling resolvents(g).
Example:
resolvents('less')
Internally the system stores the resolvents under different
mangled function names. The internal name of a function f is
obtained by calling name(f).
Example:
name(resolvents('less'))
The mangled 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 query processor resolves the correct resolvent to apply based on the types of the arguments in a function call. The type of the result is not considered. If no matching resolvent is found, an error is generated.
When overloaded function names are encountered in function bodies, the compiler will try to use local variable declarations to choose the correct resolvent at compile time. This is called early binding.
Example:
create function income(Charstring person) -> Real
as stored;
set income('Tore') = 1000;
set income('Kalle') = 2000
create function richer(Charstring p) -> Bag of Charstring
as select q
from Charstring q
where less(income(p),income(q))
richer('Tore')
Here the system will choose the resolvent with signature less(Number
i,Number j) since the stored function income returns objects of
type Real. The other resolvent less(Charstring s,Charstring t) is
not applicable since it requires strings as arguments.
Higher-order functions
OSQL functions are internally represented as any other objects and
stored in the database. Object representing functions can be used in
functions and queries too. An object representing a function is called
a functional and is represented as instances of type
Function. Higher-order
functions are
functions that take functionals as arguments or results.
Functionals
The functional constant notation #'fn' denotes the unique
resolvent for the function
named fn.
Example: this expression denotes the single resolvent for the generic
function named sin.
#'sin'
An error is raised if the function name specified as a functional
constant does not uniquely identify the function. This happens if no
function named fn exists or if it is the generic name of an
overloaded function.
Example:
#'not-a-function'
If `he functional constant #'range'is illegal, sincerange`
is overloaded. For overloaded functions the name of a
resolvent has to be used
instead.
#'range'
To get the resolvent for range(Integer u)->Bag of Integer use its
mangled name:
#'integer.range->integer'
The high-order function thefunction(Charstring fn)->Function returns
the functional having the name fn. It fails if no such function
exists.
Example: The following function call returns the object representing
the generic function range.
thefunction('range')
Dynamic function calls
The variadic function call(Function f, Object a1,Object
a2,..)->Object calls the functional f with arguments
a1,a2,.... The difference to a regular function call is that it
allows the called function to be a variable bound to functional or a
functional constant. The name of the function need not be known
beforehand.
Example: The following function applies on a bag b an arbitrary
aggregate function aggfn that returns a number.
create function bag_agg(Function aggfn, Bag b) -> Number
as call(aggfn, b)
Let's test it:
bag_agg(thefunction('avg'), bag(1,2,3))
You can use generic functions when applying non-unique resolvents, in
which case call will choose the correct resolvent based on the types
in the argument vector.
Example:
call(thefunction('+'),2,3.5)
The very general higher-order system function
apply(Function fno, Vector argl)->Bag of Vector calls the
function fno with the vector argl as argument list. It returns a bag
of result tuples represented as vectors.
Example:
apply(#'divide',[5,3])
Notice how apply() represents both argument and result tuples as
vectors. In the example the result tuple is represented by the vector
[1,2].
It is better and much more efficient to use call rather than apply
when the number of arguments in the call are known beforehand. The
query above could also be written as a variadic call that returns a
tuple:
call(#'divide',5,3)
Visit High order for a list of higher-order functions.
Deleting functions
Functions are deleted with the delete function statement.
Example:
delete function less
Deleting a function also deletes all functions calling the deleted
function, so the statement will delete both less and richer.
Example: This fails after executing delete function less:
richer('Tore')
Related documentation
The library of basic mathematical functions is documented in Math functions.
The use of aggregate functions in queries is explained in Aggregate functions in queries are treated different from other functions in queries.
The library of system aggregate functions is documented in aggregate functions.
The use of functions taking other functions as arguments is documented in High order functions.
The use of functions with side effects is documented in Procedural and stateful functions.
