The Storage Kit: BTable

Derived from: public BObject

Declared in: <storage/Table.h>


Overview

The BTable class defines objects that represent tables in a database.

A table is a template for a record, where a record is a collection of data that describes various aspects of a "thing." As a template, the table characterizes the individual datums that a record can contain. Each such characterization, which consists of a name and a data type, is called a field of the table. To make an analogy, a table is like a class definition, its fields are like data members, and records are instances of the class.

A table's definition--the make-up of its fields--is persistent: The definition is stored in a particular database. Within a database, tables are identified by name; the BDatabase class provides a function, FindTable() , that lets you retrieve a table based on a name (more accurately, the function returns a BTable object that represents the table that's stored in the database). To create a new table, you use BDatabase's CreateTable(), passing the name by which you want the table to be known (an example is given in the next section). The reliance on BDatabase to find and create tables enforces two important BTable tenets:

A subtler point regarding tables is that they don't actually contain the records that they describe. For example, every file in the Be file system is represented by a record in the database. File records contain information such as the file's name, its size, when it was created, and so on. These categories of information (in other words, the "name," "size," "creation data,") are enumerated as fields in the "File" table. But the "File" table doesn't contain the records themselves--it's simply the template that's used to create file records.


Creating a Table

As mentioned above, you create a new table (and retrieve the BTable that's constructed to represent it) through BDatabase's CreateTable() function. The function takes two arguments:

In the following example, a new table named "Employee" is created; the example assumes the existence and validity of the a_db BDatabase object:

   BTable *employee_table;
   
   /* It's a good idea to synchronize the BDatabase before
    * creating a new table.  This refreshes the object's table 
    * list.
    */
   a_db->Sync();
   
   /* Make sure the database doesn't already have an
    * "Employee" table.
    */
   if (a_db->FindTable("Employee") != NULL)
      return; /* or whatever */
   else
      /* Create the table. */
       employee_table = a_db->CreateTable("Employee");

The table name that you choose should, naturally enough, fit the "things" that the table describes. By convention, table names are singular, not plural.


Adding Fields to a Table

Having created a table, you'll want to add fields to it by calling BTable's field-adding functions. A field has two properties: a name and a data type. You pass the name as an argument to a field-creating function; the data type is implied by the function name:

Typically, you add fields only when you're creating a new table; however, you're not prevented from adding them to existing tables.

Here we add three fields to the "Employee" table; the first field gives the employee's name, the second gives the employee's telephone extension, and the third identifies the record that represents the employee's manager (this is further explained in the BRecord class description):

   field_key name_key = 
       employee_table->AddStringField("name", B_INDEXED_FIELD);
   
   field_key extension_key = 
       employee_table->AddLongField("extension");
   
   field_key manager_key = 
       employee_table->AddRecordIDField("manager");

Notice that the Add...Field() functions don't return objects. That's because fields aren't represented by objects; instead, they're identified by name or by field key, as explained in the next section (a subsequent section explains the meaning of the B_ INDEXED_FIELD argument used in the example).

You can retrieve information about a field through BTable's GetFieldInfo() functions.

Field Keys

A field key is an integer that identifies a field within its table. Field key values have the data type field_key , and are returned by the Add...Field() functions. (You can also get a field's key through the FieldKey() function, passing the field's name as an argument.) Field keys are used, primarily, when you add and retrieve BRecord data; this is taken up in the BRecord class description.

Field keys are not unique across the entire database--a field key value doesn't encode the identity of the field's table. Furthermore, a field's key value is computed on the basis of the field's name and data type. If you add, to a table, two fields that have the same name and data type (which you aren't prevented from doing), the fields will have the same field key value.

Field Flags

The optional second argument to the Add...Field() functions is a list of flags that you want to apply to the field. Currently, there's only one flag (B_INDEXED_FIELD), so the second argument is either that or it's excluded.

The presence of the B_INDEXED_FIELD flag means that the field will be considered when the database generates its index (which it does automatically). Indexing makes data-retrieval somewhat faster, but it also makes data-addition somewhat slower; the more fields that are indexed, the greater the difference on either side. In general, you should only index fields that you think will be most frequently used when data is retrieved (or fetched ).

In the example, the "name" field is indexed; this implies the predication that employee data will most likely be fetched on the basis of the employee's name. (See the BQuery class for examples of how data is fetched.)


Table Inheritance

A table can inherit fields from another table. For example, let's say you want to create a "Temp" table that inherits from "Employee". To the "Temp" table you add fields named "agency" and "termination" (date):

   BTable *temp_table;
   
   a_db->Sync();
   
   /* This time, we perform the name-collision check AND test
    * to ensure that the parent exists.
    */
   if (a_db->FindTable("Temp") != NULL ||
      a_db->FindTable("Employee") == NULL)
      return;
   
   /* Now create the table... */
   temp_table = a_db->CreateTable("Temp", "Employee");
   
   /* ... and add the new fields.  First we check to make sure 
    * we didn't inherit these fields from "Employee". The checks  
    * allow similarly named fields with different types, but not
    * fields that are identical in name -and- type.  You can
    * tighten the check to disallow fields with identical names 
    * by omitting the FieldType() check.
    */
   if ( temp_table->FieldKey("agency") != B_ERROR)
      if ( temp_table->FieldType("agency") != B_STRING_TYPE)
         field_key agency_key = 
             temp_table->AddStringField("agency");
   
   if ( temp_table->FieldKey("termination") != B_ERROR)
      if ( temp_table->FieldType("termination") != B_TIME_TYPE)
         field_key term_key = 
             temp_table->AddTimeField("termination");

The checks that accompany the field additions in the example are, perhaps, a bit overly- scrupulous, but they can be important in some situations, such as if you're letting a user define tables through manipulation of the user interface.

A table hierarchy can be arbitrarily deep. However, all tables within a particular hierarchy must belong to the same database--table inheritance can't cross databases. Also, there's no "multiple inheritance" for tables.

If you want your tables to show up in a Browser query window, the table must inherit, however remotely, from "BrowserItem". Furthermore, only those fields that start with a capital letter are displayed in the letter. Uncapitalized field names are considered private.

Note: Table hierarchies have nothing to do with the C++ class hierarchy. You can't manufacture a table hierarchy by deriving classes based on BTable, for example.


Type and App

When the user double-clicks an icon that's displayed by the Browser, the Browser launches (or otherwise finds) a particular app and then sends the clicked icon's record to the app. How does the Browser know which app to launch? If the icon represents a file, then the Browser can simply ask the file for the app's signature through the representative BFile object's GetTypeAndApp() message.

However, if the icon doesn't represent a file--if it represents a "pure" database record --then the Browser asks the record's table for its app, through BTable's GetTypeAndApp() function. When you create a new table, you set the type and app through SetTypeAndApp() . The "type" information for a table means the same thing as the "type" of a file: It's an application-specific identifier that describes the content of some data.

The type and app information for a table doesn't belong to the Browser. Any application can set and query this information.


Using a BTable

BTable objects are used in the definitions and operations of BRecord and BQuery objects. These topics are examined fully in the descriptions of those classes, and are summarized here.

BTables and BRecords

A table defines a structure for data, but it doesn't, by itself, supply or contain the actual data. To add data to a database, you must create and add one or more records. Records are created in reference to a particular table; specifically, the amount and types of data that a record can hold is determined by the fields of the table through which it's created. The record is said to "conform" to the table.

In your application, you create a record for a particular table by passing the representative BTable object to the BRecord constructor:

   /* Create a record for the "Employee" table. */ 
   BRecord *an_employee = new BRecord(employee_table);

So constructed, the an_employee object will accept data for the fields that are contained in the employee_table object. Adding data to a BRecord, and examining the data that it contains, is performed through BRecord's Set...() and Find...() functions; the set of these functions complements BTable's Add...Field() set.

BTable and BQuery

A BQuery object represents a request to fetch records from the database. The definition of a BQuery includes references to one or more BTable objects. To add a BTable reference to a BQuery, you use the BQuery AddTable() or AddTree() function. The former adds a single BTable (passed as an argument), the latter includes the argument BTable and all its descendants.

When the BQuery performs a fetch, it only considers records that conform to its BTables' tables. You can further restrict the domain of candidate records as described in the BQuery class description. Anticipating that description, here's what you do to fetch all the records that conform to a particular table:

   /* Fetch all Employee records. */
   BQuery *employee_query = new BQuery();
   
   employee_query->AddTable(employee_table);
   employee_query->PushOp(B_ALL);
   employee_query->Fetch();

To fetch all "Employee" records--including those that conform to "Temp" as well as to any other table that descends from "Employee"--we add the "Employee" table as a tree:

   employee_query->AddTree(employee_table);
   employee_query->PushOp(B_ALL);
   employee_query->Fetch();


Constructor and Destructor

The BTable class doesn't declare a constructor or destructor. You never explicitly create or destroy BTable objects; you use, primarily, a BDatabase object to find such objects for you. See the BDatabase class description.


Member Functions


AddLongField, AddRawField , AddRecordIDField, AddStringField , AddTimeField

      field_key AddLongField(char *field_name, long flags = 0)
      field_key AddRawField(char *field_name, long flags = 0)

      field_key AddRecordIDField(char *field_name, long flags = 0)

      field_key AddStringField(char *field_name, long flags = 0)

      field_key AddTimeField(char *field_name, long flags = 0)

Adds a new field to the BTable and returns the field_key value that identifies it. You supply a name for the field through the field_name argument. The flags argument gives additional information about the field; currently, the only flag value that the functions recognize is B_INDEXED_FIELD . See the section Field Keys for more information about indexing.

You declare the type of data that the field will hold by selecting the appropriate function:

  • AddRawField() declares untyped data (void * ).

  • AddLongField() declares long data.

  • AddRecordIDField() declares record_id values.

  • AddStringField() declares (char * ) data.

  • AddTimeField() declares double data.
  • Note: You use AddTimeField() to add any double-bearing field, not just fields that will hold time values. The names will be fixed in a subsequent release.

    Note that the functions don't force fields names to be unique within a BTable; you can add any number of fields with the same name. Furthermore (and slightly more concerning), you aren't prevented from adding fields that have identical names and types. Since field keys are based on a combination of name and type, this means that any number of fields in a table can have the same field key value.

    See also: GetFieldInfo()


    ChildAt()

          BTable *ChildAt(long index)

    Returns the BTable that sits in the index'th slot of the target BTable's "child table" list. Only those BTables that are direct descendants of the target are considered; in other words, a BTable doesn't know about its grandchildren. The function returns NULL if the index is out-of-bounds.

    See also: CountChildren()


    CountChildren()

           long CountChildren(void)

    Returns the number of BTables that directly inherit from this BTable.

    See also: ChildAt()


    CountFields()

          long CountFields(void)

    Returns the number of fields in the BTable; the count includes inherited fields.

    See also: GetFieldInfo()


    Database()

          BDatabase *Database(void)

    Returns the BDatabase object that represents the database that owns the table that's represented by this BTable. This is the object that was the target of the FindTable() or CreateTable() function that manufactured this BTable object.

    See also: BDatabase::FindTable(), BDatabase::CreateTable()


    FieldKey()

          field_key FieldKey(char *name)
          field_key FieldKey(char *name, long type)

    Returns the field key for the named field. The second version of the function is in case you have two fields with the same name, but different types (two fields with the same name and type can't be distinguished). The type argument must be one of the following constants:

    B_LONG_TYPE
    B_RAW_TYPE
    B_RECORD_TYPE
    B_STRING_TYPE
    B_TIME_TYPE

    Note: You use the B_TIME_TYPE for all double-field searches.

    If the named field isn't found, B_ERROR is returned.

    See also: FieldType(), GetFieldInfo()


    FieldType()

          long FieldType(field_key key)
          long FieldType(char *name)

    Returns a constant that represents the type of data that the designated field holds. The possible return values are:

    B_RAW_TYPE
    B_LONG_TYPE
    B_RECORD_TYPE
    B_STRING_TYPE
    B_TIME_TYPE

    Note: The B_TIME_TYPE is used for all double-bearing fields.

    If the field isn't found, B_ERROR is returned.

    See also: FieldKey(), GetFieldInfo()


    GetFieldInfo()

          bool GetFieldInfo(long index,
             char *name,
             field_key *key,
             long *type,
             long *flags)
          bool GetFieldInfo(char *name,
             field_key *key,
             long *type,
             long *flags)
    
          bool GetFieldInfo(field_key key,
             char *name,
             long *type,
             long *flags)

    Finds the field designated by the first argument and returns, in the other arguments, information about it. The first version identifies the field by index into the BTable's list of fields, the second by its name, and the third by its field key.

    The value returned in the type argument is one of the following constants:

  • B_LONG_TYPE

  • B_RAW_TYPE

  • B_RECORD_TYPE

  • B_TRING_TYPE

  • B_TIME_TYPE
  • Note: The B_TIME_TYPE is used for all double-bearing fields.

    The flags value will either be B_INDEXED_FIELD or 0. (See Field Keys for more information about field flags.)

    If the field isn't found, the functions returns FALSE; otherwise they return TRUE.

    See also: AddLongField()...


    HasAncestor()

          bool HasAncestor(BTable *a_table)

    Returns TRUE if the target BTable inherits (however remotely) from a_table . Otherwise returns FALSE.

    See also: BDatabase::Parent(), BDatabase::CreateTable()


    Name()

          char *Name(void)

    Returns the table's name. The name is set when the table is created.

    See also: BDatabase::CreateTable()


    Parent()

          BTable *Parent(void)

    Returns the table's parent, or NULL if none. A table's parent is designated when the table is created.

    See also: BDatabase::CreateTable()




    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.