Using Sequences in C

The C interface for sequences has two complementary layers:

A sequence function normally initializes a result iterator; it does not materialize the complete result immediately. Values are calculated as the application consumes that iterator. Consequently, errors related to individual values can be reported either when the iterator is constructed or later while it is consumed.

Define and Update Sequence Fields

The following schema defines an ordered day sequence and a parallel price sequence:

    #define MAX_SYMBOL_LEN 16

    class Quote {
        char<MAX_SYMBOL_LEN> symbol;
        sequence<uint4 asc> day;
        sequence<float>     price;

        unique tree<symbol> by_symbol;
    };

The schema compiler generates functions such as Quote_day_append(), Quote_price_append(), and Quote_price_iterator(). The following transaction fragment creates an object and appends four values to both sequence fields:

    Quote quote;
    mco_uint4 days[4] = { 1, 2, 4, 5 };
    float prices[4] = { 10.0F, 20.0F, 40.0F, 50.0F };
    MCO_RET rc;

    rc = Quote_new(trans, &quote);
    if (rc == MCO_S_OK)
        rc = Quote_symbol_put(&quote, "IBM", (mco_uint2)3);
    if (rc == MCO_S_OK)
        rc = Quote_day_append(&quote, days, 4);
    if (rc == MCO_S_OK)
        rc = Quote_price_append(&quote, prices, 4);

Applications are responsible for keeping related sequence fields aligned. Appending four days does not automatically append, remove, or validate four values in another field. An asc or desc modifier does not sort input. It causes the runtime to reject values that violate the declared order.

Use the generated insert, delete, store, and ordered search functions for positional updates and range operations. Positions and counts always refer to logical sequence elements, including when the RLE implementation is active.

Create and Consume an Iterator

Allocate an iterator for every independent traversal and initialize it to zero before passing it to an iterator constructor. A generated field iterator remains associated with its database object and transaction, so consume it before ending that transaction.

    mco_seq_iterator_t price_iterator = { 0 };
    float price;
    MCO_RET rc;

    rc = Quote_price_iterator(&quote, &price_iterator);
    while (rc == MCO_S_OK)
    {
        rc = mco_seq_next_float(&price_iterator, &price);
        if (rc == MCO_S_OK)
            printf("%g\n", (double)price);
    }
    if (rc == MCO_S_CURSOR_END)
        rc = MCO_S_OK;

mco_seq_next_TYPE() returns one logical value. It returns MCO_S_CURSOR_END after the final value. The typed traversal interface is representation-neutral: with RLE storage, it expands runs and still returns one logical value per successful call.

mco_seq_get_TYPE() can consume several values into an application buffer. On input, *size is the buffer capacity in elements; on success, it is the number of elements written.

Compose a Lazy Iterator Pipeline

Most C sequence functions take a result iterator as their first argument and save references to their input iterators. All iterators in the pipeline must therefore remain alive and unchanged until the result has been completely consumed.

    mco_seq_iterator_t price_iterator = { 0 };
    mco_seq_iterator_t absolute_iterator = { 0 };
    float value;
    MCO_RET rc;

    rc = Quote_price_iterator(&quote, &price_iterator);
    if (rc == MCO_S_OK)
        rc = mco_seq_abs_float(&absolute_iterator, &price_iterator);

    while (rc == MCO_S_OK)
    {
        rc = mco_seq_next_float(&absolute_iterator, &value);
        if (rc == MCO_S_OK)
            use_value(value);
    }
    if (rc == MCO_S_CURSOR_END)
        rc = MCO_S_OK;

Iterators are stateful consumers. Do not attach one input iterator independently to two pipelines and then expect both pipelines to see the complete input. Create two equivalent source iterators or use mco_seq_tee() when two consumers must share one traversal.

To traverse a completed pipeline again, call the reset callback installed by its constructor:

    if (absolute_iterator.reset != NULL)
        rc = absolute_iterator.reset(&absolute_iterator);

The callback resets the appropriate operand subtree. The lower-level mco_seq_reset_iterator(), mco_seq_reset_unary_iterator(), and mco_seq_reset_binary_iterator() functions are primarily building blocks for iterator implementations; applications should not guess which one a pipeline needs.

Handle NULL Elements

A nullable sequence field and nullable elements are separate properties. Generated nullable field accessors manage whether the whole field is NULL. Generated append_nullable and insert_nullable functions, and the generic get_nullable/unget_nullable functions, use a bitmap for element-level NULL values. A set bit denotes a NULL element.

When a typed mco_seq_next_TYPE() call reaches a NULL element, it returns MCO_E_SEQ_NULL_VALUE and advances past that element. The payload returned for a NULL element is unspecified and must not be read. Use mco_seq_get_nullable_TYPE() when the application needs values and their NULL bitmap in bulk.

NULL propagation depends on the operation. For example, element-wise operators preserve a NULL result for a NULL operand, while aggregate functions generally ignore NULL values and define separate behavior for empty or all-NULL input. See each function family and the Sequence Error Codes page for operation-specific contracts.

Iterator Lifetime and Allocated Buffers

Select Plain or RLE Sequences

The plain and RLE implementations export the same common C sequence symbols and are selected for the process as a pair of libraries:

Do not link both implementations into one executable as competing providers of the same symbols. When the core is loaded dynamically through mco_core_load(), pass MCO_SEQUENCE_LIBRARY_PLAIN or MCO_SEQUENCE_LIBRARY_RLE. The indirect loader accepts MCO_SEQUENCE_LIBRARY=PLAIN or MCO_SEQUENCE_LIBRARY=RLE. Use mco_sequence_library_loaded() to query the loader and mco_seq_is_rle() to query the active sequence implementation.

Typed traversal and the sequence operators hide the physical representation. Applications that call an iterator's raw next callback and read tiles directly must account for physical RLE runs. See RLE Compression and RLE C Sequence Functions.

Related C Reference Topics