Tutorial

Cursors

A Database Cursor (C++ programmers may prefer the term “iterator”) is a pointer to a Database item. You can use it to traverse the Database and to enumerate all items. You can enumerate items from front to back or vice versa. You can also use a Cursor to look up a key, or to overwrite, delete and insert items.

This chapter explains how to create and use Database Cursors.

Creating a Database Cursor

Cursors are managed using a ups_cursor_t structure. You allocate such a Cursor structure with the ups_cursor_create method. The first parameter is always the ups_db_t handle of the Database on which this Cursor operates.

The second parameter of ups_cursor_create is a Transaction handle; if you do not use Transactions, then set this parameter to NULL. Transactions are covered in a later chapter.

The third third parameter is not yet used; always set it to zero. Here is the declaration of the function:

ups_status_t
ups_cursor_create (ups_cursor_t **cursor, ups_db_t *db,
            ups_txn_t *txn, uint32_t flags);

API reference of ups_cursor_create

And here is an example of the usage; it creates a Cursor which can operate on the Database db:

ups_cursor_t *cursor;
ups_status_t st;
if ((st = ups_cursor_create (&cursor, db, NULL, 0))) {
  // …

After creation, the new Cursor does not point to any item in the Database.

Moving Database Cursors

The new Cursor is not yet of much use. Since it does not point to an item, some operations (i.e. deleting the current item) will fail. To use the Cursor, we have to position it on an item.

With ups_cursor_move, you can move the Cursor to the first, last, next or previous item in the Database. Here is the declaration of the function:

ups_status_t
ups_cursor_move (ups_cursor_t *cursor, ups_key_t *key,
        ups_record_t *record, uint32_t flags);

API reference of ups_cursor_move

This function sets the Cursor to the type which is specified in the flags. This can be one of UPS_CURSOR_FIRST, UPS_CURSOR_LAST, UPS_CURSOR_NEXT or UPS_CURSOR_PREVIOUS.

If you do not specify any direction, the Cursor will remain at the current position. (This behavior can be used to retrieve the key or record of the current item.)

If a Cursor does not point to a Database item, and you move it to UPS_CURSOR_NEXT (or UPS_CURSOR_PREVIOUS), upscaledb will automatically move the Cursor to the first (or last) element.

The key and record parameters are optional. After moving the Cursor, the key and the record of the new Database item will be retrieved copied to key and/or record, if key and/or record are not NULL.

This short example creates a Cursor and traverses the Database from the first to the last element, printing every Database key. (It is assumed that the key is of type char *, and can be printed with printf (“%s”)). Since the record of each item is not used, it is not retrieved.

ups_cursor_t *cursor;
ups_status_t st;
ups_key_t key;

if ((st = ups_cursor_create (&cursor, db, NULL, 0)))
  // handle error...

while (1) {
  st = ups_cursor_move (cursor, &key, NULL, UPS_CURSOR_NEXT);
  if (st != UPS_STATUS_OK) {
    if (st != UPS_KEY_NOT_FOUND)
      printf (“database error: %d\n”, st);
    exit (–1);
  }
  printf (“key: %s\n”, (char *)key.data);
}

Since we are only interested in the keys of the Database items, we do not request the record. Therefore, the third parameter of ups_cursor_move is set to NULL.

ups_cursor_move will return UPS_KEY_NOT_FOUND if the Cursor points to the last item in the Database and the next item is requested, or if the Cursor points to the first item and the previous item is requested.

Inserting Database Items with Cursors

ups_cursor_insert behaves the same as ups_insert. If the item is inserted successfully, the Cursor will point to the new item. Otherwise, the Cursor is not modified. Here is the declaration of ups_cursor_insert:

ups_status_t
ups_cursor_insert (ups_cursor_t *cursor, ups_key_t *key,
        ups_record_t *record, uint32_t flags);

API reference of ups_cursor_insert

And here is an example of the usage; it inserts the key “hello” and the record "world".

ups_key_t key = ups_make_key("hello", strlen("hello") + 1);
ups_record_t record = ups_make_record("world", strlen("world") + 1);

if ((st = ups_cursor_insert (cursor, &key, &record, 0))) {
  // …

Looking up Database Items with Cursors

You can use Cursors to search for Database records. The function ups_cursor_find tries to search for a key, and positions the Database cursor on the item with this key. If the item is not found, the Cursor is not modified. The flags parameter is unused - set it to 0.

ups_status_t
ups_cursor_find (ups_cursor_t *cursor, ups_key_t *key,
            ups_record_t *record, uint32_t flags);

API reference of ups_cursor_find

When the Cursor points to a Database item, you can delete the item (with ups_cursor_erase) or overwrite the record (with ups_cursor_overwrite).

Overwriting Database Items with Cursors

To overwrite the record of the current item, call ups_cursor_overwrite with a pointer to a ups_record_t structure which contains the new record. The third parameter, flags, is unused; set it to 0.

ups_status_t
ups_cursor_overwrite (ups_cursor_t *cursor, ups_record_t *record,
            uint32_t flags);

API reference of ups_cursor_overwrite

If you try to overwrite an item while the Cursor does not point to an item, upscaledb returns UPS_CURSOR_IS_NIL.

Deleting Database Items with Cursors

To delete a record using a Cursor, position the Cursor on the record you want to delete, then call ups_cursor_erase. The third parameter, flags, is unused; set it to 0.

ups_status_t
ups_cursor_erase (ups_cursor_t *cursor, uint32_t flags);

API reference of ups_cursor_erase

If you try to delete an item, but the Cursor does not point to an item, upscaledb returns UPS_CURSOR_IS_NIL.

If the delete operation was successful, the Cursor will be uninitialized and not point to any item at all.

Cloning a Database Cursor

upscaledb provides a function for cloning an existing Cursor. ups_cursor_clone creates an exact copy of a cursor which points to the same Database item as the original Cursor and is part of the same Transaction.

ups_status_t
ups_cursor_clone (ups_cursor_t *src, ups_cursor_t **dest);

API reference for ups_cursor_clone

Cloning Cursors is a very cheap operation and does not cost any performance.

Closing a Database Cursor

When you finished working with a Database Cursor, you have to close and free it. This will prevent memory leaks. All Cursors must be closed before the Database is closed.

ups_status_t
ups_cursor_close (ups_cursor_t *cursor);

API reference of ups_cursor_close