A very basic operation in most database applications is calculating a value from two or more other values. Like the FETCH family of operations, this operation is so natural to most programmers that they do not think much about it.
Andromeda supports calculated values by using our CHAIN automation. This automation has a different syntax than others, because it supports more possibilities.
A very simple example is this snippet from an order_lines table:
table orderlines:
# these foreign keys are standard on any
# orderlines table
foreign_key orders:
foreign_key items:
# We have already seen this in prior examples
column price:
auto:fetch,items.price
# The user enters this value
column qty:
# Now we perform the calculation
colum price_extended:
suffix: _extended
chain calc:
test 00:
return: @price * @qty
For calculated values, the calculation always begins with the exact definition "chain calc:", which identifies the properties that follow as a calculation chain (Andromeda also supports constraint chains, that's why it is not simply "chain").
The next line after "chain calc:" is indented to the next level. This next line begins with the keyword "test" followed by a number. In this example this may seem like extra typing, but we will see in the next examples why that is there.
Finally, the third line is indented another level and specifies the return calculation. Note that field names are preceeded by the "@" sign.
The trivial example above might give the impression that you can put any valid SQL expression into the return expression. This is not the case. There are precise rules for building the return expressions, which will be detailed next.
The CHAIN syntax allows for conditional processing, which is its real power. In fact, this is the only place in Andromeda where you can put conditionals into the automations -- because it is the only place you need it!
The example below deals with a case where some items are taxable and some are not. We can imagine an ITEMS table that has a flag that marks each item as taxable or not. This means that sales tax must be considered line by line, it is not a simple calculation based on all lines. The following example shows how to do this.
table orderlines:
# ...details...
# standard foreign keys for this
# kind of table
foreign_key orders:
foreign_key items:
# These columns are explained above
column qty:
column price:
auto: fetch,items.price
# this is a new flag we must fetch
# for each item
column flag_taxable:
suffix: _taxable
auto: fetch,items.taxable
# Now we multiply out two values,
# the first is the taxable amount,
# the second is non-taxable. Some
# lines will have one value, others
# will have the other.
column price_ext_taxable:
suffix: _ext_taxable
chain calc:
test 00:
compare: @flag_taxable = Y
return: @qty * @price
test 01:
return: 0
column price_ext_notax:
suffix: _ext_notax
chain calc:
test 00:
compare: @flag_taxable = N
return: @qty * @price
test 01:
return: 0
# For completeness, here is how the order
# header would look:
table orders:
# ...details...
# First we sum the values from the orderlines
column price_ext_taxable:
suffix: _ext_taxable
auto: sum,orderlines.price_ext_taxable
column price_ext_notax:
suffix: _ext_notax
auto: sum,orderlines.price_ext_notax
# An order always has a foreign key to
# customers of course, so fetch the customer's
# tax authority code from there
foreign_key orders:
column taxauth:
auto: fetch,customers.taxauth
# Now fetch the tax percentage from a
# a table of tax authorities
foreign_key taxauths:
column pct_tax:
suffix: _tax
auto: fetch,taxauths.taxrate
# Now calculate the tax
column amt_tax:
suffix: _tax
chain calc:
test 00:
return: @pct_tax * price_ext_taxable
# ..and finally the order total
column price_final:
chain calc:
test 00:
return: @price_ext_taxable + @price_ext_notax @amt_tax
In this example we see that each column has two "test" lines, instead of just one. We also see that the first test line contains a new property, the "compare" expression. These work just how you would think, at runtime the comparisons are made one by one until the first one returns true. Once a comparison returns true, the return value is calculated. At the end is the default case calculation, the one that returns if none of the tests matched.
The syntax for the compare and return expressions is not very flexible, by the standards of most languages and by the rest of Andromeda. We hope to rectify this in a future release, but at least through Release 1 the following restrictions apply:
The supported operators for comparisons are these:
| Operator | Description |
|---|---|
| EMPTY | Returns true if value is empty (empty string or zero numeric or null date) |
| !EMPTY | Opposite of empty |
| BETWEEN | First value is between next two |
| !BETWEEN | Opposite of BETWEEN |
| NULL | Value is null |
| !NULL | Value is not null |
| IN | First value matches any of following values |
| !IN | Opposite of IN |
| >, >=, < <= =, <> | Standard comparisons, work for all types. |
The supportec calculation operators are:
| Operator | Description |
|---|---|
| +, -, *, / | Add, subtract, multiply and divide |
| CON | String concatenation |
| CONU | String concatenation inserting underscores between segments |
| EXTRACTYEAR | Year part of date |
| EXTRACTEPOCH | Epoch part of date |
| EXTRACTMONTH | Month part of date |
| EXTRACTDAY | Day part of date |
| REPLACE | String replace. First argument is subject, 2nd argument is search string, third argument is replacement value. |
| LPAD, RPAD | Pad out on left or right. The first argument is the subject, no other arguments are necessary, it pads to the length of the target column. |
| SUBS | Substring. First argument is subject, 2nd is starting point and third is length. |
| UPPER, LOWER | Convert string to upper or lower case. |
| BITAND | Bitwise AND |
| BITOR | Bitwise OR |
| BITXOR | Bitwise XOR |
| BITNOT | Bitwise negation |