The Storage Kit: BRecord

Derived from: public BObject

Declared in: <storage/Record.h>


Overview

A BRecord represents a record in a database. A record is a collection of values that, considered together, describe a single, multi-faceted "thing." The thing that a record describes depends on the table to which the record conforms. For example, each record that conforms to the "File" table would describe different attributes of a specific file: its name, size, the directory it's contained in, and so on. A single record can store as much as 32 kilobytes of data (but, to be safe, you should try to keep your records a wee bit smaller than that).

A BRecord object lets you examine and modify the values that are collected in a record. But first, you have to associate the BRecord object with the record that you want to inspect or alter. How you make this association depends on whether you're creating a new record that you wish to add to the database, or retrieving an existing record from the database. These topics are discussed separately in the following sections.


Creating a New Record

You create a new record in reference to a specific table (within a particular database). In your application, you create this reference by passing a BTable object to the BRecord constructor. For example, the following code constructs a BRecord object that conforms to the "Employee" table (the table was created in an example in the BTable class description):

   /* We'll assume the existence of the a_db BDatabase object. */
   BTable *employee_table = a_db->FindTable("Employee");
   BRecord *employee_record = new BRecord(employee_table);

By conforming to a BTable, a BRecord is given appropriately-sized "slots" that will hold data for each of the fields defined by the table. For example, the "Employee" table (as defined in an example in the BTable class description) has three fields:

The employee_record object, therefore, can accommodate values for these three fields. In a freshly created BRecord, the value for each field is NULL (cast as appropriate for the data type of the field).

Important: You must explicitly delete the BRecord objects that you construct in your application. Some of the operations that a BRecord performs (such as committing or removing) might lead you to think that you've "given" the object to the Storage Server, and that you're absolved from the responsibility of destruction. You haven't; you're not.

Setting Data in the BRecord

To put data in a BRecord object, you use its Set...() functions; these functions are named for the type of data that they implant:

  • SetLong() places a long value in the BRecord.

  • SetDouble() places a double value.

  • SetString() copies a string.

  • SetRecordID() places a a record_id value.

  • SetTime() places a double that measures time since January 1, 1970.

  • SetRaw() copies an arbitrarily long buffer of "raw" data (type void *).
  • Each of these functions designates, as its first argument, the table field that's used to refer to the data. There are two ways to make this designation: by a field's name, or by its field key (as defined by the BTable class).

    Continuing our employee record example, we begin to put data in the new BRecord by setting data for the "name" and "extension" fields:

       /* We'll designate the "extension" field by the field's name.  */
       employee_record->SetLong("extension", 123);
       

    For variety, we'll set the "name" field by its field key:

       field_key name_key = employee_table->FieldKey("name");
       employee_record->SetString(name_key, "Mingo, Lon");

    In most cases, there's no difference between the two methods of designating a field (by name or field key); you can use which ever is more convenient. The one instance in which there is a distinction is if you have a table with similarly named fields that are typed differently, in which case, the fields will only be distinguishable by field key. For example, if your table has a long field named "info" and a string field that's also named "info", you would distinguish between the fields by using their keys.

    Committing a BRecord

    The data that you set in a BRecord isn't seen by the database (and so can't be seen by other applications) until you commit the data through BRecord's Commit() function:

       record_id mingo_id = employee_record->Commit();

    The function sends the object's data back to the Storage Server, which places it in the database; the Server creates a new record to hold the data if necessary. The record_id value that the function returns uniquely identifies the record within its database (as explained in the next section).

    Important: Notice that the BRecord in the example was committed with an "empty" field: The "manager" field hasn't yet been set. Because this is a new record, the value at this field is, by default, NULL. Unfortunately, there's no way to distinguish between a default NULL and a legitimate NULL. For example, if our "Employee" table included a long "vacation days" field, the value (for that field) could legitimately be 0 --it would look the same as NULL. You wouldn't be able to tell if the value was accurate, or if the field hadn't yet been filled in.


    Record ID Numbers

    A record is identified, within its database, by a record ID number (type record_id): Every record in a given database has a different record ID. A BRecord knows the record ID of the record it represents (you can get it through the ID() function). But keep in mind that a record ID identifies a record, not a BRecord; thus:

    Record ID Fields

    One of the features of the record_id type is that it can be used to define a table field. Just as you can declare a table field to accept long or string data, you can declare a field to take record ID values (through BTable's AddRecordIDField() function). Through the use of a record ID field, one record can point to another record. Although the two records must reside in the same database, the two records needn't conform to the same table. In fact, you can't designate, in the field definition, the table that the pointed-to record conforms to.

    Returning to the example, the "manager" field in the "Employee" table is typed as a record_id field. To set the value for this field in our employee record, we need to find the record ID of Lon Mingo's manager. This is a job for a BQuery object, as explained in that class.


    The Record Ref Structure

    The record_ref structure is similar to the record_id number: It identifies a record in a database. The difference between these two entities is that the record_ref structure encodes the record ID and the database ID (the ID of the database in which the record resides); the structure's definition is

    A record ref (or, simply, "ref") is, therefore, more exacting in its identification of a record than is the record ID. So why would you use a record ID if a ref is more precise?

    Comparing Refs

    The record_ref structure defines the == and != comparison operators. This allows you to compare two refs as values. For example, the following is legal:

       record_ref a = a_record->Ref();
       record_ref b = b_record->Ref();
       
       if (a == b)
          ...

    For two refs to be equal, their database fields must be the same and their record fields must be the same.


    Retrieving an Existing Record

    In addition to creating new (potential) records for you, the BRecord constructor can retrieve an existing record from a database. To do this, you pass a BDatabase object and record ID to the constructor:

    BRecord(BDatabase *a_database, record_id record)

    Typically, you fetch the record ID numbers that BQuery object and tell it which records to fetch. The object retrieves record ID numbers which you then use here to actually get records. (See the BQuery class for information on fetching.)

    Data Examination

    To examine the data in a BRecord, you ask for the value of a specific field (as defined by the object's BTable). This is accomplished by functions that take this form:

    FindType(field_key key )
    Find
    Type(char *field_name)

    where Type is one of the six data types that a field can take ( ergo FindLong(), FindDouble(), FindRaw() , FindRecordID(), FindString(), and FindTime()). Each function has two versions so you can designate the field by field key or by name. The functions return the field's data directly. The two pointer-returning functions (FindRaw() and FindString() ) return pointers to data that's owned by the BRecord. You shouldn't try to modify BRecord data by writing to these pointers.

    Updating a BRecord

    Keep in mind that when you examine a BRecord's data, you're looking at the object's copy of the data that exists in the actual record in the database. If the actual record changes--if a field's value is modified, or if the record itself disappears--such changes are not automatically reflected in the BRecord objects that point to the record. ("Live" queries, as explained in the BQuery class, help in this regard, as they inform your application when a change is made.)

    If you want to be sure you have the most recent data in your BRecord before you examine it, you should call the Update() function. Update() re-copies the record's data into your BRecord object. Note, however, that any uncommitted changes that you've made to the BRecord are lost when you update.

    Data Modification

    Modifying data in a BRecord is also done in reference to specific fields. The suite of modification functions mirrors those for examination, but with an additional argument that specifies the value you want to set:

    SetType(field_key key , data_type value)
    Set
    Type(char *field_name, data_type value)

    For example, the functions that set long data are:

    SetLong(field_key key, long value)
    SetLong(
    char *field_name, long value )

    The changes that you make to the object's data aren't sent back to the database until you call Commit(). The one exception to this is if you remove the record altogether (through the Remove() function). You don't have to call Commit() after you call Remove().


    Extra Fields

    In addition to the fields that are defined by its table, a record can contain "extra" fields. Extra fields are created and removed dynamically (through a BRecord object) without affecting the record's table definition. Extra fields are identified by name only, and the data they contain is always untyped (it's considered to be raw).

    To add an extra field to a BRecord, you use the SetExtra() function. The function takes three arguments: The name of the field that you're adding, the data that you want the field to contain, and the length of the data. For example, here we add two extra fields to the employee record:

       employee_record->SetExtra("motto", 
                            (const void *)"I Am Mingo",
                            strlen("I Am Mingo"));
       
       long age = 35;
       employee_record->SetExtra("age",
                            (const void *)&age,
                            sizeof(long));
       employee_record->Commit();

    To find the data in an extra field, you pass the name of the field to FindExtra(). The function returns a pointer to the data as it lies in the BRecord object --it doesn't copy the data. The function also returns the amount of data (in bytes) that the extra field is storing:

       char *m_ptr, *motto;
       long *a_ptr, age, size;
       
       if ((m_ptr = (char *)FindExtra("motto", &size)) == NULL)
          ...
       else {
          motto = malloc(size+1); /* Add 1 for the NULL */
          strncpy(motto, m_ptr, size);
          motto[size] = '\\0'; 
       }
       
       if ((a_ptr = (long *)FindExtra("age", &size) == NULL)
          ...
       else 
          age = *a_ptr;

    BRecord supplies the function GetExtraInfo() so you can discover the names and sizes of a record's extra fields. The function takes an index ( n) as its initial argument and returns a pointer to the n'th extra field's name and the size of the field in its second and third arguments. It returns B_ERROR if the index is out-of-bounds. Here, we use the function to iterate over all the extra fields in a record:

       char *name;
       long size;
       long i = 0;
       
       while (employee_record->GetExtraInfo(i++, &name, &size) 
                != B_ERROR) 
          printf("Extra Field Name: %s; Size %d\\n", name, size);

    A single record can contain any number of extra fields; the only restoration is that they all must have different names. If you call SetExtra() using a name that already exists, the existing data is removed and the new data is installed. On the other hand, an extra field can have the same name as a field in the record's table: The BRecord object keeps the two sets of fields separate, so it won't get confused.

    Since extra fields aren't part of a table definition, you can't declare them to be indexed (as the term is used in the BTable class), and you can't use them in a query predicate (see the BQuery class).


    Constructor and Destructor


    BRecord()

          BRecord(BDatabase *database, record_id id)
          BRecord(record_ref ref)
    
          BRecord(BTable *table)
    
          BRecord(BRecord *record)

    Creates a new BRecord object and returns it to you.

    The first version of the constructor (the BDatabase and record_id version) is used to acquire the record with the given ID from the specified database. The second version does the same, but encodes the database and record identities as a single record_ref value.

    The second version (BTable) constructs a BRecord that can accommodate values for the fields that are declared in its BTable argument.

    The third version copies the data from the argument BRecord into the new BRecord (including the ref value).

    You should follow a call to the constructor with a call to Error() to make sure the specified record was found or created; the function returns B_ERROR for failure and B_NO_ERROR for success.

    See also: Error()


    ~BRecord()

          ~BRecord(void)

    Frees the memory allocated for the object's copy of the database data. The object is not automatically committed by the destructor; if there are uncommitted changes, you must explicitly commit them or they'll be lost.

    Note that you are responsible for deleting the BRecords that you've constructed. When you commit or remove a record (when you call Commit() or Remove()), you're not giving the object to the Server.


    Member Functions


    Commit()

          record_id Commit(void)

    Sends the BRecord's data back to the database. The function returns the record_ref of the record that the object represents. It does this as a convenience for new records, which will be receiving fresh ref numbers; "old" records (records that were previously retrieved from the database) don't change ref values when they're committed.

    You should call Error() immediately after calling Commit() to see if the operation was successful ( B_NO_ERROR). It will fail (B_ERROR) if the ref isn't valid, if the record has been locked by some other object, or if some other obstacle bars the path of ingress.

    See also: Lock() , Update()


    Database()

          BDatabase *Database(void)

    Returns the BDatabase object that represents the database that owns the table that defines the record that killed the cat that ate the rat that's represented by this BRecord.


    Error()

          long Error(void)

    Returns an error code that symbolizes the success of the previous call to certain other functions. The following functions set the code that's returned here:

  • the BRecord constructor

  • Commit()

  • Update()

  • FindLong(), FindString(), ...

  • SetLong(), SetString(), ...

  • Remove()
  • In all cases, a return from Error() of B_NO_ERROR means that the previous call was successful; B_ERROR means it failed.

    After Error() returns the error code is automatically reset to B_NO_ERROR.


    FindDouble(), FindLong() , FindRaw(), FindRecordID() , FindString(), FindTime()

          double FindDouble(char *field_name)
          double FindDouble(field_key key)
    
          long FindLong(char *field_name)
          long FindLong(field_key key)
    
          void *FindRaw(char *field_name, long *size)
          void *FindRaw(field_key key, long *size)
    
          record_id FindRecordID(char *field_name)
          record_id FindRecordID(field_key key)
    
          const char *FindString(char *field_name)
          const char *FindString(field_key key)
    
          double FindTime(char *field_name)
          double FindTime(field_key key)

    These functions return the value of the designated field in the BRecord. None of these functions check to make sure you're returning the value in an appropriate data type, nor do they perform any type conversion.

    FindRaw() and FindString() return pointers to data that's owned by the object. If you want to manipulate or store the data, you must copy it before deleting the object. The FindRaw() functions also return, by reference in size, the amount of data that it points to.

    You should always check Error() after calling one of these functions to make sure the call was successful. The usual culprit, in a failure, is an illegitimate field specification. Asking for the value of a non-existing field, for example, will fail.

    There is a subtle difference between the field name and field key versions of these functions: If you ask for a value by field name, the data type given by the selected function is used to locate the correct field. For example, if the "age" field stores long data but you ask for its value as a string ...

       char *ageString = FindString("age"); 

    ... the function won't be able to find a string-valued "age" field and so will fail ( Error() will return B_ERROR ). The analogous request by field key:

       char *ageString = FindString(a_table->FieldKey("age"));

    won't appear to fail (Error() returns B_NO_ERROR), even though it will return something awful.

    See also: SetDouble()


    FindExtra() see SetExtra()


    GetExtraInfo() see SetExtra()


    IsNew()

          bool IsNew(void)

    Returns TRUE if the object was constructed to represent a new record, and hasn't yet been committed.

    See also: the BRecord constructor


    Ref()

          record_ref Ref(void)

    Returns the record_ref structure of the BRecord's record. This structure uniquely identifies the record across all databases. This function always returns a record_ref value, even if the BRecord has never been committed (in which case the structure's record field will be -1).


    Remove()

          void Remove(void)

    Removes the BRecord's record from the database. The success of the removal is reported in the value returned by Error() ( B_NO_ERROR if the record was removed, B_ERROR if it wasn't).


    RemoveExtra() see SetExtra()


    SetDouble(), SetLong(), SetRaw(), SetRecordID(), SetString(), SetTime()

          void SetDouble(char *field_name, double value)
          void SetDouble(field_key key, double value)
    
          void SetLong(char *field_name, long value)
          void SetLong(field_key key, long value)
    
          void SetRaw(char *field_name, void *ptr, long size)
          void SetRaw(field_key key, void *ptr, long size)
    
          void SetRecordID(char *field_name, record_id value)
          void SetRecordID(field_key key, record_id value)
    
          void SetString(char *field_name, char *ptr)
          void SetString(field_key key, char *ptr)
    
          void SetTime(char *field_name, double value)
          void SetTime(field_key key, double value)

    Sets the value of the designated field to the value given by value . These functions don't perform type checking or type conversion. (See FindDouble() for more information on fields and types; the rules described there apply here.)

    SetRaw() and SetString() copy the data that's pointed to by their ptr arguments. The SetString() pointer must point to a NULL-terminated string. You specify amount of data (in bytes) that you want SetRaw() to copy through the function's size argument. Keep in mind that you can only store 32 kilobytes of data in a single record (in all its fields combined).

    To gauge the success of the modification, check the value returned by Error(). If the field's value was successfully set, Error() returns B_NO_ERROR ; otherwise it returns B_ERROR.

    The value-setting functions don't affect the actual record that the BRecord represents: When you call a SetType() function, you're modifying the BRecord's copy of the data, not the actual data that lives in the database. This means that you're able to successfully call these function if the record is locked, and if the BRecord doesn't (yet) have a ref (conditions under which many other functions fail). To write your change to the database, you call BRecord's Commit() function.

    Keep in mind that a subsequent Lock() call will wipe out the (uncommitted) changes that you've made through these functions. This is an important point since many applications will want to lock before committing. If you plan on locking, you should do so before using these functions. In other words:

       /* Lock, modify, commit, unlock. */
       a_record->Lock();
       
       a_record->SetLong("age", 9);
       a_record->SetString("name", "Emma");
       ...
       a_record->Commit();
       a_record->Unlock();

    See also: FindLong()


    SetExtra(), FindExtra(), RemoveExtra(), GetExtraInfo()

          void SetExtra(const char *name, const void *data, long dataLength)
          void *FindExtra(const char *name, long *dataLength)
          void RemoveExtra(const char *name)
          long GetExtraInfo(long index, char **name, long *dataLength)

    These functions add, query, and remove the BRecord's "extra" fields. Extra fields can be added and removed dynamically; they aren't part of the definition of the table to which the record conforms. Extra fields are identified by names and can hold an arbitrary amount of untyped data. The names of a record's extra fields must be unique among themselves, but can be the same as the record's "normal" (table-defined) fields. For examples of these functions, see Extra Fields .

    SetExtra() creates a new extra field named name, or replaces the existing so-named field. The data that the field holds is copied from data; the amount of data to copy is given by dataLength. The extra data that you add through this function must be committed (through the Commit() function) just like "normal" data.

    FindExtra() finds the field named name and returns a pointer to its data (directly). The length of the data is passed back by reference through the dataLength argument. Keep in mind that the function gives you a pointer to data that's owned by the BRecord. You shouldn't modify or free the pointer. If FindExtra() can't find the named field, it returns NULL.

    RemoveExtra() removes the named field.

    GetExtraInfo() retrieves information about the index'th extra field: A pointer to the field's name is returned in *name , and the length of the field's data is returned in *dataLength. You shouldn't allocate *name before passing it in--the pointer that's passed back points into the BRecord itself. By the same token you mustn't free or modify * name. If index is out of bounds, GetExtraInfo() returns B_ERROR, and sets *name to point to NULL . Otherwise, it returns B_NO_ERROR.


    Table()

          BTable *Table(void)

    Returns the BTable to which the BRecord conforms.


    Update()

           void Update(void)

    Copies the record's data from the database into the BRecord. Any uncommitted changes you have made to the data that's currently held by the BRecord will be lost. The success of the update is reported by the value returned by the Error() function (B_NO_ERROR means success; B_ERROR indicates failure).




    The Be Book, HTML Edition, for Developer Release 8 of the Be Operating System.

    Copyright © 1996 Be, Inc. All rights reserved.

    Be, the Be logo, BeBox, BeOS, BeWare, and GeekPort are trademarks of Be, Inc.

    Last modified September 6, 1996.