RML follows the principles of the C++ Standard Library. RML does not provide any locking whatsoever, on the general C++ principle to not incur overheads when something is not needed. It is therefore the responsibility of the calling program to ensure adequate thread safety.
As a general rule, iterating results should not be interleaved with modifying data.
Non-modifying operations can be interleaved and run concurrently. Modifying operations may not be performed on the same table concurrently as another operation. The modifying operations are members of relational::table. They are:
If you decide to modify a row directly without using these methods, you are on your own. table::update_row() should be used at all times, since it has no overheads for an unindexed column.
RM_DEFINE_ROW_1(RowType, Type1, Name1)
RM_DEFINE_ROW_2(RowType, Type1, Name1, Type2, Name2)
RM_DEFINE_ROW_3(RowType, Type1, Name1, Type2, Name2, Type3,
Name3)
...
Purpose: Defines a row type.
Parameters:
· RowType: The name of the type to be defined.
· TypeN: The type of the Nth member of the row.
· NameN: The name of the Nth member of the row.
Requirements: This macro must be used at global or namespace scope. It cannot be used at function or class scope. Columns must provide a default constructor, a copy constructor and an assignment operator.
Effects: Defines a struct with the name RowType, in the current namespace. The struct has the members Name1… NameN as public members with their specified types.
The struct also has a default constructor, a copy-constructor and an assignment operator. It has a constructor of the form
RowType(const Type1 &, ... const TypeN&)
that allows RowType to be initialized with the values of the columns.
Exceptions: The constructors are exception-neutral. The assignment operator is the compiler-default, which is only exception-safe.
Non-member functions:
template<typename T, typename Comparator = std::less<T> >
class indexed;
Purpose: A template that contains value of type T. The purpose of indexed is to act as a tag informing the table that this column has an index on it.
Parameters:
· T is the data type to be stored.
· Comparator is the comparator used for the index.
Members:
· typedef Comparator comparator_type;
· typedef T value_type;
template<typename T, typename Comparator = std::less<T> >
class unique;
Purpose: A template that contains value of type T. The purpose of unique is to act as a tag informing the table that this column has an index on it.
Parameters:
· T is the data type to be stored.
· Comparator is the comparator used for the index.
Members:
· typedef Comparator comparator_type;
· typedef T value_type;
struct duplicate_key : public std::runtime_error
{
duplicate_key() : std::runtime_error("Duplicate key") { }
};
Purpose: This is an exception thrown whenever an attempt is made to insert a duplicate key into a table. It can be thrown from table::insert(), table::insert_ex(), table::update_where(), and table::update_column().
struct not_found : public std::runtime_error
{
not_found() : std::runtime_error("Not found") { }
};
Purpose: This is an exception is thrown by find_ex() when no result is found.
template<typename Row, typename Traits = table_traits<Row> >
class table;
Purpose: A table is a container that stores rows. The indexing of the table is specified by Row, using the indexed and unique column types, which also supply the comparator for the index. The table will automatically maintain all indexes upon insertion, updating and deletion, and enforce uniqueness on unique columns.
table implements model Results. table is a C++ container.
Parameters:
· Row is the type of row that the table stores.
· Traits is a traits class to configure the table. It specifies the allocator, which defaults to std::allocator<Row>.
Requirements:
Complexity:
· N represents the number of items in the table.
· I is the number of indexes on the table.
· L is the cost of performing a lookup. Will be O(ln N) if an index is used or O(1) if an index is not used.
· S is the number of items scanned by a query. 0<=S<=N. Will be 0 or 1 if using a unique index. Will be N if not using an index.
· Q is the cost of performing a query. Q=L + O(S).
· M is the number of items matched by the query. M<=S.
· U is the cost of updating a column, either O(1) or O(ln N).
Members:
· value_type
· iterator
· const_iterator
o Purpose: Same as iterator.
· size_type
· difference_type
o Purpose: A type to store the difference between two iterators.
· reference
o Purpose: A reference to value_type.
· const_reference
o Purpose: A const reference to value_type.
· table()
o Purpose: Constructs the table with no members.
· table(const table & other)
· template<typename Other> table(const Other & other)
o Purpose: Copy constructor.
o Effects: Copies all rows from the other table into the current table.
· table &operator=(const table & other)
· template<typename Other> table &operator=(const Other & other)
o Purpose: Copies the contents of other, replacing the current contents.
o Exceptions: Exception neutral. Throws exceptions on row allocation or constructor failure.
o Returns: *this
· const_reference insert(const_reference row)
o Effects: Inserts a row into the table. It stores a copy of row in the container.
o Parameters: row is the data to be inserted.
o Exceptions: Exception neutral. Will throw relational::duplicate_key if a key is duplicated on a unique column. If an exception is thrown, the row is not inserted. Can throw std::bad_alloc from the allocator, plus other exceptions from the column copy constructors.
o Returns: Returns an iterator to the inserted row.
o Complexity: O(I.ln N). The row must be inserted into each of the I indexes.
· value_type * insert_nothrow(const_reference row)
o Effects: Inserts the row into the table. A copy of row is stored in the table.
o Parameters: row is the data to be inserted.
o Returns: Returns 0 if insertion failed due to a duplicate, or an iterator to the inserted row.
o Exceptions: Exception neutral. Will still throw std::bad_alloc if memory allocation fails, and will throw from the row copy constructor. If an exception is thrown the row is not inserted. The name of the function is misleading, since it can still throw exceptions.
o Complexity: O(I.ln N)
· size_type erase_where(const Condition& condition)
o Effects: Removes all rows from the table matching the condition.
o Parameters: condition is the condition used to match rows to erase.
o Complexity: O(Q + M.I.ln N). The condition is used to locate rows to erase, and for each matched row, it is removed from each index in the table.
o Exceptions: Should not throw exceptions.
o Returns: The number of rows erased.
· size_type update_where(const Assignment &assignment, const Condition& condition)
o Effects: Performs the assignment for each row matching the condition.
o Parameters: condition is a condition specifying which rows to match. assignment specifies how to update each matching row.
o Exceptions: Throws duplicate_key if a duplicate is inserted into a unique index. The index will be in a consistent state, but the query is not rolled back (RML is not transactional for performance reasons). Not exception safe for assignment exceptions.
o Complexity: O(Q + M.U). The condition is used to locate rows to update, and then the row is updated according to the assignment.
o Returns: The number of rows updated.
o Notes: There are special cases where updating the same column as the query column can cause an infinite loop. For example
customers.update_where(customers.address = “123 The Lane”, customers.id == 9)
is completely safe,
customers.update_where(customers.id = 12, customers.id == 8)
is also safe (because the implementation always performs a left-insert into the tree), however
customers.update_where(customers.id = customers.id + 12, all())
is likely to go into an infinite loop, while
customers.update_where(customers.id = customers.id - 12, all())
will work okay because new nodes are inserted before the iterator. Be careful when assigning values dependent on the row.
· template<int Column> void update_row(const row_type &row, const type &value)
· void erase() throw()
· void erase_row(const row_type & row) throw()
o Effects: Removes the given row from the table.
o Notes: This operation is potentially dangerous since it will break the current query and result in undefined behaviour. Do not continue iterating the current query after calling this function.
o Parameters: row is the row to erase.
o Preconditions: The row must be in the table.
o Exceptions: No exceptions are thrown.
o Complexity: O(I.ln N)
· size_type size() const throw()
· bool empty() const throw()
· iterator begin() const throw()
· iterator end() const throw()
· void swap(table &other) throw()
o Effects: Exchanges all members of two tables.
o Parameters: other is the table that is swapped.
o Exceptions: Does not throw.
o Complexity: O(1)
· template<typename Other> bool operator==(const Other&other) const
o Purpose: Compares two tables.
o Returns: True if the two containers have the same number of elements, and each element is equal in the same sequence.
o Requirements: All columns must provide an operator==, and the columns in other must be comparable to the columns in the table.
o Exceptions: Exception neutral. Should not throw exceptions.
· template<typename Other> bool operator!=(const Other&other) const
o Purpose: Compares two tables.
o Returns: !operator==().
o Requirements: All columns must provide an operator==, and the columns in other must be comparable to the columns in the table.
o Exceptions: Exception neutral. Should not throw exceptions.
· template<typename Other> bool operator<(const Other&other) const
o Purpose: Compares two tables.
o Returns: True if the table is lexographically less than other.
o Requirements: All columns must provide an operator<, and the columns in other must be comparable to the columns in the table.
o Exceptions: Exception neutral. Should not throw exceptions.
· template<typename Other> bool operator<=(const Other&other) const
o Purpose: Compares two tables.
o Returns: !(other<*this)
o Requirements: All columns must provide an operator<, and the columns in other must be comparable to the columns in the table.
o Exceptions: Exception neutral. Should not throw exceptions.
· template<typename Other> bool operator>(const Other&other) const
o Purpose: Compares two tables.
o Returns: other<*this
o Requirements: All columns must provide an operator<, and the columns in other must be comparable to the columns in the table.
o Exceptions: Exception neutral. Should not throw exceptions.
· template<typename Other> bool operator>=(const Other&other) const
o Purpose: Compares two tables.
o Returns: !(*this<other)
o Requirements: All columns must provide an operator<, and the columns in other must be comparable to the columns in the table.
o Exceptions: Exception neutral. Should not throw exceptions.
Non-member functions:
· Stream &operator<<(Stream&s, const table&t)
o Effects: Writes an entire table to a stream.
o Parameters: s is the stream to write to. t is the table to write.
o Requirements: Stream can be wide or narrow. Requires that operator << is available for all columns. std::basic_string is treated separately.
o Exceptions: Exception safe. Exceptions will cause the results to be partially written.
o Complexity: O(N).
· Stream &operator>>(Stream&s, table&t)
o Effects: Reads an entire table from a stream. Items are added to the table, the existing contents are not removed.
o Parameters: s is the stream to read from. t is the table into which results are read.
o Requirements: Stream can be wide or narrow. Requires that operator >> is available for all columns. std::basic_string is handled separately.
o Exceptions: Exception safe. Will throw relational::duplicate_key, and will leave the table in a consistent state with only the rows prior to the exception added. May throw std::bad_alloc, plus exceptions generated by the comparators (if any).
o Complexity: O(M.I.ln (M+N)) where M is the number of rows inserted, and N is the number of items already in the table.
Purpose: A Results model is used to represent a set of results. Results represents a list of rows from a fixed number of tables T1..Tn. The results contain the rows of a single table, or from a multi-table join. The tables and columns in Results are determined at compile-time, while the actual contents are determined at run-time.
Results is nearly a C++ container. However it may lack an assignment operator, a copy constructor and a swap() function. The complexity of the size() function can be O(N), not O(1).
Exceptions: Exception neutral. Results does not modify data.
Members:
· value_type
· iterator
· const_iterator
o Purpose: Same as iterator.
· size_type
· difference_type
o Purpose: A type to store the difference between two iterators.
· reference
o Purpose: A reference to value_type.
· const_reference
o Purpose: A const reference to value_type.
· size_type size() const throw()
· bool empty() const throw()
· iterator begin() const throw()
· iterator end() const throw()
· template<typename Other> bool operator==(const Other&other) const
o Purpose: Compares two tables.
o Returns: True if the two containers have the same number of elements, and each element is equal in the same sequence.
o Requirements: All columns must provide an operator==, and the columns in other must be comparable to the columns in the table.
o Exceptions: Exception neutral. Should not throw exceptions.
· template<typename Other> bool operator!=(const Other&other) const
o Purpose: Compares two tables.
o Returns: !operator==().
o Requirements: All columns must provide an operator==, and the columns in other must be comparable to the columns in the table.
o Exceptions: Exception neutral. Should not throw exceptions.
· template<typename Other> bool operator<(const Other&other) const
o Purpose: Compares two tables.
o Returns: True if the table is lexographically less than the container other.
o Requirements: All columns must provide an operator<, and the columns in other must be comparable to the columns in the table.
o Exceptions: Exception neutral. Should not throw exceptions.
· template<typename Other> bool operator<=(const Other&other) const
o Purpose: Compares two tables.
o Returns: !(other<*this)
o Requirements: All columns must provide an operator<, and the columns in other must be comparable to the columns in the table.
o Exceptions: Exception neutral. Should not throw exceptions.
· template<typename Other> bool operator>(const Other&other) const
o Purpose: Compares two tables.
o Returns: other<*this
o Requirements: All columns must provide an operator<, and the columns in other must be comparable to the columns in the table.
o Exceptions: Exception neutral. Should not throw exceptions.
· template<typename Other> bool operator>=(const Other&other) const
o Purpose: Compares two tables.
o Returns: !(*this<other)
o Requirements: All columns must provide an operator<, and the columns in other must be comparable to the columns in the table.
o Exceptions: Exception neutral. Should not throw exceptions.
Purpose: A ResultsIterator is used to iterate the results of a query. In general, relational::for_each should be used to iterate through the results of a query.
ResultsIterator is a C++ forward iterator.
Although begin(), end() and operator!=() can be used to iterate a container, it is often more efficient to use operator bool() to determine when there are more results. This is more efficient for the types of iterator that RML implements.
Exceptions: ResultsIterator is exception neutral. ResultsIterator is an observer which does not modify data.
Members:
· iterator_category
o Purpose: The category of the iterator, generally, std::forward_iterator_tag.
· value_type
o Purpose: The type of the row.
· difference_type
o Purpose: The distance between two members.
· pointer
o Purpose: A const pointer to value_type.
· reference
o Purpose: A const reference to value_type.
· iterator()
o Purpose: Default constructor.
· iterator(const Results&r)
· operator bool() const
· reference operator *() const
o Requirements: The iterator must not be in the “end” state.
o Returns: The current row. If there are several rows in each result, the first row is returned.
o Complexity: O(1)
o Exceptions: Does not throw exceptions.
o Notes: This is the same as get_row<0>().
· pointer operator ->() const
· iterator &operator ++()
· iterator &operator ++(int)
o Requirements: The iterator must be valid.
o Effects: Preincrement.
o Exceptions: Exception safe.
o Returns: *this
o Complexity: Defined by the query.
· bool operator ==(const iterator &) const
· bool operator !=(const iterator&) const throw()
· template<int N> const row_type_n &get_row() const
o Requirements: 0<=N< number of tables in the result. Otherwise the program will not compile. The iterator must be valid.
o Parameters: N is the row to return.
o Returns: A const reference to the nth row of the result.
o Exceptions: Does not throw.
o Complexity: O(1)
template<typename TableRefList, typename Condition>
Results select(const TableRefList &tables,
const Condition &condition)
Purpose: Query a table or a list of tables.
Parameters:
· tables is a table, or a comma-delimited list of tables.
· conditions is the condition used to filter the results.
Returns: The return value is a Results object (a type that implements model Results).
Exceptions: Exception neutral. selec()t is an observer.
Requirements: The condition should be valid for the query. That means that the column must be present in the query, and relational::col should specify an existent column. For example the condition
points.y == 0
is perfectly legitimate in
select ( points, points.y == 0 )
but will fail to compile for
select ( customers, points.y == 0 )
Comparators must be present for the compared columns. Failure to observe these requirements will result in a compilation failure.
Description: Each result contains a row from each table in the query. This is known as a join, whereby every combination of rows is returned, also known as a cross product. For example if we don’t supply a condition and call
select( (A,B,C), all() )
then the return value contains every combination of A, B and C, totalling A.size()*B.size()*C.size() results.
When a condition is supplied, the result is a (possibly empty) subset of the cross-product. The evaluation is as if every combination of A, B and C is tested against the condition, and the condition acts as a filter to accept or reject particular combinations of rows.
The actual implementation is a lot more efficient than a brute-force search. An indexed column is chosen for each table in the query. If there are two possible indexes, the column used first (leftmost) in the condition is the one that is used.
In multi-table joins, the table order specifies the order in which the tables are searched. Tables are searched in a depth-first search order. For example
select( (A, B, C), condition)
is implemented
for a in A using index matching condition
for b in B using index and matching condition
for c in C using index and matching condition
…
Therefore ordering the tables is very important for efficiency. In general, execution should be thought of as proceeding left to right.
Complexity: For a single seek, the complexity of this function is O(Q1*Q2*Q3...) where Qn is the cost of performing a seek in the nth table of the join. Qn may be O(1), O(ln N) or O(N) depending upon the query and the indexes used.
Memory consumption: O(1).
Non-member functions:
· TableList relational::operator ,(const Table & t1, const Table & t2) throw()
· TableList relational::operator ,(const TableList & l1, const Table & t2) throw()
template<typename Column, typename Results>
Results2 sort(const Column & col, const Results & results)
Purpose: Sorts results on column number Column of table number Table.
Parameters:
Requirements: Column must specify a valid column in Results. Otherwise the program will not compile.
Memory consumption: O(M), where M is the number of results. If a sort of a single table is performed, on an indexed column, then the memory consumption is O(1).
Returns: A Results object containing the results in a sorted order.
Exceptions: Can throw std::bad_alloc if the intermediate array could not be allocated.
Complexity: O(Q) + O(M.ln M). The cost of performing the query plus an additional cost of sorting the results. Only pointers are sorted, not the actual contents of the rows. Can be O(Q) if a table is indexed on the column, so no post-processing is required.
Example:
for_each( sort(customers.name, customers), visit );
template<typename Column, typename Results>
Results2 sort_descending(const Column&col, const Results&results)
Purpose: Sorts results in descending order.
Parameters:
Requirements: Column must specify a valid column in Results. Otherwise the program will not compile.
Returns: A Results object sorted by the column.
Memory consumption: O(M), where M is the number of results.
Exceptions: Can throw std::bad_alloc.
Complexity: O(Q) + O(M.ln M). The cost of performing the query plus an additional cost of sorting the results. Only pointers are sorted, not the actual contents of the rows.
Example:
// visit tests in descending order of grade
for_each( sort_descending(test.score,
select( (test, students),
test.student_id == students.student_id)),
visit);
template<typename Results>
Results2 limit(const Results &results,
unsigned count=1, unsigned offset=0)
Purpose: Limits the number of results from a query.
Parameters:
Returns: A Results container that contains the limited results. The returned results contains a sub-list of the original results, in original sequence, starting at item number offset and containing count items.
Exceptions: Exception neutral.
Example:
// Select the top 10 grades
limit(sort_descending(grades.score, grades), 10);
// Select the next 10 grades
limit(sort_descending(grades.score, grades), 10, 10);
template<typename Column, typename Results>
column_type sum(const Column & col, const Results &results)
Purpose: Find the sum of a column of a set of results.
Parameters: col specifies the column to sum. results are the items to sum.
Requirements: col must be a valid column in results, otherwise the program will not compile. The data type of the column must have a + operator.
Returns: The sum of the particular column in Results. column_type is the type of the column in the Results. Will return zero if no results are found.
Exceptions: Exception neutral. sum is an observer.
template<typename Results, typename Visitor>
void for_each(const Results &results, Visitor &visitor)
Purpose: The foreach function calls visitor for each result in results. The Visitor object must implement operator(), which is called for each result in results. If there are no results, visitor is not called.
Parameters: results are the results to visit. visitor is a functor that is called for each result in results.
Requirements: The Visitor::operator() function should accept the same number of arguments as there are tables in the query, and each argument to operator() is a const reference to a row from each of the tables in the query.
Example:
// Define the visitor
class add_to_window
{
CWindow &window;
public:
add_to_window(CWindow &win) : window(win) { }
void operator()(const Customer & customer,
const Order & order)
{
window.add_item ( customer.name,
order.date, order.value);
}
};
// Issue a query and call the visitor
for_each(select( (customers, orders),
customers.id == orders.customer_id),
add_to_window(window));
template<typename Results>
const row_type *find(const Results &results)
Purpose: Finds the first result in a Results container.
Parameters: results is the container of results.
Returns: A pointer to the first row in results. If there are multiple tables in results, the row of the first (leftmost) table is returned. row_type is the type of the first row of the results.
Exceptions: Exception neutral. Does not raise exceptions.
Example:
if(Customer *c = find(
select(customers, customers.name == “John”)
) )
{
// Do something with c
}
else
{
// Report not found
}
template<typename Table, typename Results>
const row_type *find_table(const Table&t, const Results &results)
Purpose: Finds a specified table in the first result of a Results container. This is for multi-table results, where the table of interest is not the first table in the join.
Parameters: t is the table to select, results is the container of results.
Requirements: t must be a valid table in for Results, otherwise the program will not compile.
Returns: A pointer to the specified table if a result was found. 0 if no result was found. row_type is the row type of the specified table.
Exceptions: Exception neutral. Does not generate exceptions.
Example:
const Student *student = find_table(students,
sort_descending(tests.score,
select( (tests, students),
tests.student_id == students.student_id)
) );
template<typename Results>
const row_type &find_ex(const Results &results)
Purpose: Finds the first result in a Results container.
Parameters: results is the container of results.
Returns: A const reference to the first row of the results. If results contains multiple tables, a reference to the row of the first (leftmost) table is returned.
Exceptions: Exception neutral. Throws relational::not_found if results is empty.
Example:
const Student &student =
find_ex( select(students, students.id == 12) );
template<typename Table, typename Results>
const row_type &find_table_ex(const Table &t, const Results &r)
Purpose: Finds the first row of a specified table in a Results container.
Parameters: t is the table to return, r is the container of results.
Requirements: t must be a valid table in r, otherwise the program will not compile.
Returns: A const reference to the specified row. row_type is the type of the row of the specified table.
Exceptions: Exception neutral. Throws relational::not_found if results is empty.
template<typename Table, typename Results>
Results2 select_table(const Table&t, const Results &results)
Purpose: Transforms results into a Results object that contains a single table as specified by the template parameter Table.
Parameters: t specifies the table number, results is the results to transform.
Requirements: results implements model Results. t must be a table in results or the program will not compile.
Returns: A Results container with the same number of results as results. Results2 is an internal type that implements model Results.
Exceptions: Exception neutral. Does not generate exceptions.
Complexity: Compile-time.
template<typename Column, typename Results>
type select_column(const Column &col, const Results &results)
Purpose: Selects a single column from a Results container.
Parameters: col is the column to select, results is the results to select from.
Requirements: col must be a valid column in results, or the program will not compile.
Returns: A container with a forward iterator. The container contains the same number of results as results.
Exceptions: Exception neutral. Does not generate any exceptions.
Complexity: Compile-time.
template<typename Results>
void output_results(const Results &results,
std::ostream &os = std::cout)
Purpose: A utility to display all of the results in a Results container. Useful for debugging queries.
Parameters: results are the results for display. os is the stream to write the results to.
Exceptions: Exception safe. Results partially written if there is an exception.
Purpose: Represents an abstract expression that can be used as a value for use in queries. These expressions are not evaluated immediately, rather they are evaluated as the query is performed.
Members:
· Expression operator +(const Expression&)
o Returns: An expression representing the sum of two expressions.
· Expression operator –(const Expression&)
o Returns: An expression representing subtraction.
· Expression operator *(const Expression&)
o Returns: An expression representing multiplication.
· Expression operator /(const Expression&)
o Returns: An expression representing division.
· Expression operator %(const Expression&)
o Returns: An expression representing modulus.
· Condition operator ==(const Expression&)
o Returns: A condition representing the equality of two expressions.
· Condition operator =(const Expression&)
o Returns: A condition representing equals, or assignment.
· Condition operator <=(const Expression&)
o Returns: A condition representing less-than or equals.
· Condition operator <(const Expression&)
o Returns: A condition representing less-than.
· Condition operator >=(const Expression&)
o Returns: A condition representing greater-than or equals.
· Condition operator >(const Expression&)
o Returns: A condition representing greater than.
· Condition operator !=(const Expression&)
o Returns: A condition representing not equal to.
Purpose: Represents a condition for use in a query. The condition is not evaluated immediately, but is executed during iteration. Condition is also analysed by the query analyzer to determine which indexes to use when performing a query.
Members:
· Condition operator &&(const Condition&)
· Condition operator ||(const Condition&)
· Condition operator !()
Condition all()
Purpose: A condition that is always true.
Returns: A condition. The return type implements model Condition.
Condition none()
Purpose: A condition that is always false.
Returns: A condition. The return type implements model Condition.
Example:
count(select( (A,B) , all() && none())) == 0
template<int Col1, int Col2=-1>
class col
Purpose: relational::col can be used to specify a column in a query. One or two template parameters can be specified. With one parameter, the template parameter Col1 specifies a column, and the table number is 0. With two parameters, Col1 specifies the table, and Col2 specifies a column.
col<> can also used for specifying assignments in relational::table<>::update_where().
Under normal circumstances, it would be preferable to refer to a column by name.
Parameters: Col1 and Col2 specify a table and column in a query.
Requirements: Col1 and Col2 may have any value. However if they are out of range for a given context (e.g. they specify a non-existent column in a query) then the program will not compile.
Returns: col implements model Expression. This means that it implements comparison operators, allowing queries to be constructed using the overloaded operators.
Examples:
select( customers, col<customer_surname>() > “smith”)
select ( (customers, orders),
col<0, customer_surname>()>”smith” &&
col<0, customer_id>() == col<1, order_customer>() )
customers.update_where( col<debt>()=true,
col<account>() < 0 && col<payment_due>() < april);