User Reference

Thread safety and concurrent access

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_N

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 Name1NameN 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:

relational::indexed

      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;

relational::unique

      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;

 

relational::duplicate_key

      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().

relational::not_found

      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.

relational::table

      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 Resultstable 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.

Model: Results

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.

 

Model: ResultsIterator

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)

relational::select()

      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