class ref name: orthodox
category-group: orthodox
layer: 6
header file: z_orthodox.h
libraries: libz00.lib libz01.lib libz02.lib libz03.lib libz04.lib libz05.lib libz06.lib

synopsis.
The orthodox object, or orthodox_o, serves as a foundation class for application-oriented objects. You can create your own "orthodox object" by subclassing from the this class. It provides the plumbing for being able to make your objects permanent. You can save your objects to a database. Then, when your program ends, you can fetch the objects afterwards. Long-term storage capability is the main reason for the orthodox class.

The "orthodox" class (orthodox_o) is a fairly complex object and needs some explaining. In the Z Directory, the orthodox objects represent concrete, "real-world" (and "computer-world") things. However, this is not a requirement. Basically most c++ clases that need the capability for their object instances to be able to be stored into a SQL database table are candidates to become orthodox objects. Relational database operations are handled by the [more primitive] "dbbi" (dbbi_o) and dbtable_o classes. The orthodox object introduces a subsystem and its own protocols. In mechanical terms, orthodox_o is a base class for storing recursive data bag instances into an RDBMS. It is a [grandchild] sub-class of the rec_dbag_o class, and adds to it database storage capability.

It may appear that you are using an object-oriented database, but in fact this class maps your objects to a regular plain-vanilla SQL-based relational database. One limitation is that, although orthodox is a sub-class of the recursive data bag class (which can structure data in complex ways), the data of an orthodox object must be flat; basically, name-value pairs. On the other hand, an orthodox object can "own" other objects. For example, if there is an object to represent an airplane, you can make other orthodox objects for airplane components, such as wings, rudders, or passenger seats, and mates those components to the airplane instance. The sub-parts will be stored in their own tables, but will reference the airplane object.

description.
Data for an orthodox class object is kept in the grandparent class - in a recursive data bag. The recursive databag is initialized, configured, molded, modified and tweaked to no end by the orthodox_o class. Sandwiched in between the orthodox object (orthodox_o) and the recursiave databag object (rec_dbag_o) is the dbtable object. dbtable_o plays a very minor role in the orthodox framework. One may think of the orthodox class as having hands that reach through dbtable, basically ignoring it, in order to reach the databag behind the intermediary class.

When a class is sub-classed from the orthodox class, it must provide a description of the database table schema. This schema is stored in a string, but the format of the contents is that of a data bag. When you make a subclass from the orthodox class, you will probably make a [.h] header file and a primary source code [.c or .cpp] file for the object. The string containing the database schema for the object is intended to be kept in the source code file of the class. This is a plus from a software project maintenance and architectural point of view, since normally the database table definition for an object is put into a separate file, often far away from the object's source code. In this case, no separate SQL code needs to be maintained. Table schema and the source code is found in one location.

To get data in and out of the database, use the following member functions:

  • store_fetch(): get (load) a single object from the database;
  • store_nfetch(): a re-entrant function, intended to be put inside a loop, that gets the next object from a set of retrieved database records
  • loop,
  • store_update(): updates changed values to the database;
  • store_add(): adds the object to the database. the success of this call depends on the schema, such as unique fields and data constraints. returns the column 'id' value (or -1 on error).
  • store_remove(): removes all rows (objects) from the databaes matching the criteria given to this function call.

There are some standard fields common to all orthodox classes (that is, objects sub-classed from orthodox): "id", "owner", "owner_tabid", and a few others. There are 7 such fields:

  • id: this is a unique record number for the object. Its value is assigned by the orthodox class.
  • owner: this is a reference to another "id" field in another table, if the object is owned by another object. A non-zero value conveys ownership. For example, an e-mail address or a telephone number may be owned by a person [object] or a business [object].
  • owner_tabid: this is a number representing the database table of the owner. In the case of a telephone number may be owned by a person, this value would represent the internal database ID of the "person" table.
  • t_start: this is intended to represent when the object "started". For a person, it could be date of birth. For employment, it could be the worker's start date. For a country, it could be the date of independence.
  • t_end: this is intended to represent when the object ended. It is analogous to the 't_start' field, and should be set by the application.
  • found: this is a date-type field representing when the object was "found" or discovered. In the context of sales force automation, this typically represents when a prospect was first found by the sales agent. By default, this is set to when the record is added to the database. This is intended to be set by the application.
  • source: This is a short text-type field provided to indicate how the object was found, and relates to the 'found' field. If the object was "found", this should be set to the source of information as to how the object was found (web site url, contact name, etc). It can be set to any text.
The fields "owner" and "owner_tabid" provide a means to mate a "re-parentable object" to its rightful owner. Re-parentable objects can belong to heterogeneous types of owners. For example, a phone number can belong to a person, a business, or a house. At the database level, if we have a table for phone numbers, we shouldn't tie it to a specific table. Another implementation could be to make separate tables for a business's list of telephones, a person's telephones, etc (an even worse way would be to de-normalize the relationship by imbedding the phone numbers in the business table).

The following data bag is used for setting up common database fields. Note how the entire text block is wrapped up as a (char *):

const char *object_TP =
"orthodox\n\
(\n\
    class_name orthodox\n\
    columns\n\
    [\n\
<name    type qual      nulls_ok is_uniq ref_table ref_column default check>\n\
<id          int  \"serial\"         NO  YES    \"\"    \"\"    \"\"   \"\">\n\
<owner       int  \"\"               NO  NO     \"\"    \"\"    \"0\"  \"\">\n\
<owner_tabid int  \"\"               NO  NO     \"\"    \"\"    \"0\"  \"\">\n\
<t_start datetime \"year to second\" YES NO     \"\"    \"\"    \"\"   \"\">\n\
<t_end   datetime \"year to second\" YES NO     \"\"    \"\"    \"\"   \"\">\n\
<found      date  \"\"               YES NO     \"\"    \"\"    \"\"   \"\">\n\
<source  varchar  80                 YES NO     \"\"    \"\"    \"\"   \"\">\n\
    ]\n\
    indexes ( )\n\
    primary_key < >\n\
    foreign_key\n\
    [\n\
        <source_column dest_table dest_column>\n\
    ]\n\
    sub_tables < >\n\
    parent\n\
    (\n\
        class_name \"\"\n\
    )\n\
)";
As can be deduced from the above data bag, the schema for your orthodox object must not have fields that collide with the default fields (above): name, id, owner, owner_tabid, t_start, t_end, found, and source. If any of these column names are found in your schema, they will be discarded! There is a check in the orthodox object for such matching bags. Also, if your orthodox object has sub-tables (instances of other objects, like a parts assembly), the

Your application needs to supply a set of fields that describe rows in the table of the SQL database. These rows must match the structure of the databag above (eg, path 'orthodox columns'). For example, internet address object (internet_address_o) has a database table layout based on this data bag, which is almost entirely composed of a matrix databag:

internet_address
(
    columns
    [
<name type qual nulls_ok is_uniq ref_table ref_column default check>
<octet0    smallint \"\" YES  NO \"\"    \"\"    \"\"    \"\" >
<octet1    smallint \"\" YES  NO \"\"    \"\"    \"\"    \"\" >
<octet2    smallint \"\" YES  NO \"\"    \"\"    \"\"    \"\" >
<octet3    smallint \"\" YES  NO \"\"    \"\"    \"\"    \"\" >
<note         char   40  YES  NO \"\"    \"\"    \"\"    \"\" >
<location     char   40  YES  NO \"\"    \"\"    \"\"    \"\" >
<options      char    5  YES  NO \"\"    \"\"    \"\"    \"\" >
<free_text    char   90  YES  NO \"\"    \"\"    \"\"    \"\" >
    ]
)
These rows (in the matrix databag) will be merged with the default rows of the skeleton orthodox object. Other things in the SQL database table, such as indexes and primary keys, can be put into this specification.

Although the internal databag structure is rather complex, accessing the object's data is easy, using simple databag get() and put() functions (this sliver is based on the example found on this page):

void main()
{
    job_offer x;
    int ie = x.store_fetch("posting_ID='3547874120'");
    x.put ("url", "www.jobserve.com");
    string_o currpay = x.get("raw_pay");
}

Some of the pre-supplied database table fields are provided as a convenience to the application, such as "start", "end", and "found". The meanings of start and end are defined by the application. The semantics of the fields should be intuitive. For example, "start" for person can mean birth time/date, but "start" for telephone may mean when the owner-user started to claim the telephone (as opposed to when the telephone was created or sold). If the application does not provide definitions for these fields, it defaults to when the record was created (or deleted) in the database. The "found" field is a time-stamp, representing when an object was "found", from the application's point of view. Sometimes this happens to coincide with a program, such as a web spider pulling in new data. The spider "finds" the object at a certain point in time. This field should always be defined and controlled by top-level (application) logic.

Adding other database constructs: indexes.
In order to prevent store_add() from adding a duplicate item, you can specify a unique index for the object. For example, a proxy is uniquely identified by the host ("dns") name + port number. For this class, the index section of the database databag looks like this:

   indexes \
    ( \
        proxy_idx \
        ( \
            is_cluster NO  \
            is_unique  YES \
            is_ascend  NO  \
            is_descend NO  \
            columns < dns_name port > \
        ) \
    ) \
In this example, "is_unique" is set to YES, ensuring that the index comprised of the 2 fields 'dns_name' and 'port' is unique. YOu can flavor this as needed; all index parameters have values of either YES or NO. In this example, this will map onto the following SQL statement (validated for SQL Server):
  create unique index proxy_idx on proxy (dns_name, port);

Ownership; start - end dates. The Z Directory has some people-oriented classes that are derived directly from the orthodox class, such as business (business_o), worker (worker_o), and person (person_o). The term 'worker' was finally settled on to mean a person that works (in some capacity) for a business. "Employee" was deemed inadequate because it would exclude a contractor, or possibly an executive such as a V-P, director, or even the owner. When an orthodox object falls out of scope (such as a person dying, or a worker getting fired), the corresponding record in the database is not deleted. Rather, the "end" (date) field is set. Likewise, when an object gets a new owner, no database record is deleted. Instead, table fields representing the owner are updated. An example illustrates these principles.

Suppose John Doe sits in chair number 501 and uses a telephone with phone number 201-5348. He gets angry at his boss, steals the chair, and quits on 7/6/2002. The boss buys a new chair, and Tatyana Uroda is hired. She starts to work on 11/25/2002. She receives what was formerly John Doe's telephone (201-5348). At this point in the database, there are 2 rows in the 'worker' table, 2 person rows, and 2 telephone rows. The first rows in the telephone and worker tables apply to John Doe, with "end" value of 7/6/2002. The second telephone and worker rows are for Tatyana Uroda, with start value 11/25/2002, and the end value is null (assume she is still working with the same chair and telephone).

If John Doe starts working at the cement factory on 7/7/2002, A new row is added to the worker table: start = 7/7/2002, where worker.ID is the person.ID value for John Doe. The worker.owner_ID is that of the business.ID field for the cement factory, and the "owner_table" field's value is "business".

Adding subtables.
The example presented in the section above ("Ownership; start - end dates") involving John Doe and Tatyana Uroda, provided some insight into a very important concept in the Z Directory orthodox framework: subtables and reparentable database tables.

An object of your creation may own other objects that rightfully belong in other tables if you follow Codd's database normalization practices. Often such objects need not "belong" to your class that you have created. For example, a telephone number may belong to a person or a busines, which are clearly two distinct classes. Suppose an instance of a person (eg, a specific human) who has a row in the "person" table has a telephone, and a telephone number associated with it. The corresponding row in the person table will also have a row in the phone_number table. This row will have for its owner field the ID value of the person in the person table. Likewise, if a business has a specific telephone number (usually answered by the receptionist), this is represented by a row in the phone_number table that has in its owner field the ID value of the business in the business table. Also, the phone_number row can be transferred between a person and a business (and possibly other tables). Thus, the parent can change - hence, such data is called reparentable.

When creating your orthodox object, you can specify what objects can belong (in the sense given by the examples above) to your new orthodox sub-class by adding the table name in the sub_tables list. Suppose your class is "patient", relating to medical procedure insurance claims processing. A patient can have an entry (or possibly many entries) in the illness table and an entry in the insurance_policy table. What is owned by what is entirely up to how you set up your framework. You may decide that only a patient can have an illness. Later, you might create an orthodox class called "animal" and also include illness as a subtable to "animal". The example below omits most of the important things, such as column definitions, focusing on subtables only:

patient
(
    columns
    [
<name type qual nulls_ok is_uniq ref_table ref_column default check>
    ]
    sub_tables 
)
You must also do this: somewhere near the start of the constructor of your orthodox object, instantiate one instance of the object corresponding to each subtable. Do this before calling init_ortho(). If for example the object corresponding to the "patient" class is Patient, the objects for the two subtables are illness_o and insurepol_o, and the string enveloping the patient schema is called Patient_TP, your code should resemble the following:
static char *Patient_TP = "patient ( /* SCHEMA STRING */ )";
static rec_dbag_o PatBag(Patient_TP);

Patient::Patient() { static boolean do_onceonly = FALSE; if (!do_onceonly) { illness_o ill; insurepol_o pol; do_onceonly = TRUE; } init_ortho(PatBag); }

By simply instantiating the subtable objects, they will be included in the list of known orthodox objects, which will then allow automatic creation of the database tables upon the first orthodox_o::store_add() call of the Patient class.

problems with subtables. When an item that belongs to an instance of a parent - for example, a worker object (worker_o) can contain [multiple] phone and e-mail objects - when you update the sub-object (eg an e-mail address), the parent object won't be "notified". Suppose you have a phone instance, and you change the phone number. Code-wise, it may look like this:

  int ie0, ie1;
  worker_o wx;
  string_o new_phno = "(510) 548-3047";
  ie0 = wx.store_fetch("id = 468");
  const phone_number_o &pho = wx.get_nth_phone (0, &ie1);
  if (!ie1)
    ie0 = wx.update_nth_value ("phone-number", new_phno, 0);
  ie0 = wx.store_update();      // <== WON'T UPDATE THE PHONE #
The phone number object instance will need to be manually updated. Ideally this would be done automatically; perhaps in the future. A workaround has been devised for the here and now. In the orthodox object, there is a flag (of type boolean), 'my_autosync', which corresponding getters and setters:
  • is_autosync() - is the object in "auto-sync" mode?
  • set_autosync() - turn on/off "auto-sync" mode
The application (or the class using the orthodox object as a base class) can use this flag, and if set, do the database update at the appropriate time (eg, inside update_nth_value()).
This flag is, by default, set.

Fetching records. When an object is fetched from the database, all fields are extracted and put into the orthodox object's internal databag. When an object is updated to the database, only those [databag] fields that have changed are saved, back to the same database record. Use store_fetch() to retrieve a single object from the database. A call to this member function is expected to provide a "where clause". If the where clause is omitted (which will default it to an empty string), the objet's ID is used as a basis for the search criteria:

    class MyThing: public orthodox_o
    {
        // ...
    };
    void foobar()
    {
        int ie0;
        MyThing x;
        x.set_externID(88);
        ie0 = x.store_fetch("");        // do fetch w no where clause
    }
This will retrieve only any matching record who's ID value is 88. To get any old record in the "MyThing" table, use store_nfetch():
    void foobar()
    {
        int ie0, ie1;
        MyThing x;
        x.set_externID(0);              // not a valid datsbase ID
        dbbi_o dx0;                     // set up a dbbi accessor
        count_t idx = 0;
        string_o wc = "";               // empty where clause
        ie0 = ev.store_nfetch(dx0, idx, 0, wc, "date_occured ASC", &ie1);
    }

In this case, the first record with the highest-values "date_occured" field will be retrieved. If there are no records in the table, ie0 will be set to 1. In both cases, the where clause is an empty string. However, store_fetch() will resort to searching for an ID, whereas store_nfetch() will not.

Adding records to a database. When an object is added to the database, all fields are included as one big SQL insert statement. The object is added as a single row to a database table by the same name as assigned to the object (there are plans for the future to be able to map to a different table name; for now, the table name is limited to the exact object name). After the object itself is added, any "sub-objects" are added to their respective tables. In the case of a business object, the number of "sub-objects" can be very large: the business's address(es), phone number(s), e-mail addresses, event list, etc.

There is a specific caveat that needs to be addressed when adding a sub-object to a database: the object's store_add() function will not be called on when adding the object as a sub-object. Instead, the base class function will be used, ie orthodox_o::store_add(). This is a problem when adding events to a business. Consider adding an event with no parent object:

    int ie0;
    stime_o t("11/17/2013");
    event_o ev;
    ev.set(t, "This event is free-floating.. no owner.\n");
    ie0 = ev.store_add();
In this case, event_o::store_add() will be invoked, which checks for conflicts with other events in the database's event table. When the event is added to the database as part of a business, this check is not done:
    int ie0;
    stime_o t("11/17/2013");
    business_o b;
    event_o ev;
    .
    b.put_value ("full_name", "Acme Construction, Inc.");
    ev.set(t, "This is an 'Acme' business event.\n");
    b.add_event(ev);
    b.store_add();
In this case, business_o::store_add() will be invoked for the business object ("b") but orthodox_o::store_add() will be used for its event. This is because sub-objects are reduced to simple databags after they are submitted to their master-owner objects. Polymorphism will unfortunately be lost during the store_add() operation. Unfortunately, there is no solution. Any custom hooks for processing the data must be done explicitly by the application before giving the object to an owner-master.

modified data.
Databags automatically track what's been modified. Suppose you have the following databag:

Engine
(
    type Internal_Combustion
    carburator
    (
        mfr Holley
        capacity "750 cfm"
        barrels 4
    )
    block
    (
        mfr Ford
        cylinders 8
        capacity "427 ci"
        compression "12.01"
        distributor "dual point mechanical advance"
    )
)
And now suppose you change the distributor in the scheme above:
x.put ("block distributor", "single point vacuum advance");
This would automatically change the state of the simple databag denoted by the path "block distributor" to modified. It would also change the recursive databag "block" and the top-level recursive databag (ie, "Engine") also to modified.

If the above databag was part of a "car" orthodox object, and you stored the object to a database (via store_add()), all the databags that comprise a car need to have their states be set to "unmodified". This is also true after doing store_update(). Thus, pushing an orthodox object to a database (or retrieving one from a database) puts the state of all the data to "unmodified".

member functions (primary)

orthodox_o()
SIGNATURE: orthodox_o ()
SYNOPSIS:
creates a a new orthodox object. The instance is loaded with this [default] recursive data bag:
orthodox
(
  orthodox_zone
  (
    class_name  orthodox
    is_ok       NO
    sub_tables  < >
    id          0
    owner       0
    owner_tabid 0
    parent
    (
        name ""
        id          0
        owner       0
        owner_tabid 0
    )
  )

 

orthodox_o(orthodox_o)
SIGNATURE: orthodox_o (const orthodox_o &that)
SYNOPSIS:
creates a a new orthodox object; it is an exact image of 'that', the [RHS] copied orthodox object. The ID of 'that' is also inherited by the object.
 

operator = (orthodox_o)
SIGNATURE: const orthodox_o &operator = (const orthodox_o &that)
SYNOPSIS: copies exactly the RHS object 'that'. The resultant orthodox object instance is the same as if the copy constructor was used.
 

destructor
SIGNATURE: ~orthodox_o ()
SYNOPSIS: virtual destructor. The object is wiped out. All internal contents is deleted.
 

orthodox_o(<args>)
SIGNATURE: orthodox_o (const textstring_o &ts, int x = 0)
 

orthodox_o(<args>)
SIGNATURE: orthodox_o (const char *, int = 0)
 

orthodox_o(<args>)
SIGNATURE: orthodox_o (enum Ortho_Flag)
 

ID()
SIGNATURE: count_t ID() const
SYNOPSIS: returns the "local" ID of the object.
TRAITS: this function is inlined
 

externID()
SIGNATURE: inline count_t externID () const
SYNOPSIS: returns the "external" ID of the object. This is basically used to get the object's ID in the database.
TRAITS: this function is inlined
 

is_ok()
SIGNATURE: inline boolean is_ok () const
SYNOPSIS: xxx
TRAITS: this function is inlined
 

table_schema()
SIGNATURE: virtual rec_dbag_o table_schema (int *) const
SYNOPSIS: xxx
 

operator !=()
SIGNATURE: int operator != (const orthodox_o &) const
SYNOPSIS: xxx
TRAITS: this function is inlined
 

operator ==()
SIGNATURE: int operator == (const orthodox_o &) const
SYNOPSIS: xxx
 

num_rows()
SIGNATURE: count_t num_rows (int *pi = NULL)
SYNOPSIS:
if a lookup was done earlier (store_fetch() or store_nfetch()), you can find out how many rows were found. this function is analogous to dbbi_o::num_rows().
PARAMETERS

  • pi: [output] error indicator variable. values:
    0: value returned ok;
    zErr_PriorOpNotDone: no store_fetch()-type operation was done prior
  •  

    get_value()
    SIGNATURE: const string_o &get_value (const string_o &, int *) const
    SYNOPSIS: xxx
     

    class_name()
    SIGNATURE: const string_o &class_name () const
    SYNOPSIS: xxx
    TRAITS: this function is inlined
     

    parent_class()
    SIGNATURE: const string_o &parent_class () const
    SYNOPSIS: xxx
    TRAITS: this function is inlined
     

    operator <<()
    SIGNATURE: friend std::ostream &operator << (std::ostream &os, const orthodox_o &o)
    SYNOPSIS: xxx
     

    set_owner()
    SIGNATURE: int set_owner (const orthodox_o &, int * = NULL)
    SYNOPSIS: xxx
     

    set_ID()
    SIGNATURE: int set_ID (count_t)
    SYNOPSIS:
    sets the "internal" ID of the object. The internal ID is used to identify "local instances", that is, variables in the current process space. This differs from the IDs of objects stored in the database. To reference those, use externID and set_externID.
     

    force_ID()
    SIGNATURE: int force_ID (count_t)
    SYNOPSIS: xxx
    TRAITS: this function is inlined
     

    set_externID()
    SIGNATURE: int set_externID (count_t)
    SYNOPSIS: xxx
    TRAITS: this function is inlined
     

    set_class_name()
    SIGNATURE: int set_class_name (const string_o &)
    SYNOPSIS: xxx
    TRAITS: this function is inlined
     

    set_table_autocreate()
    SIGNATURE: boolean set_table_autocreate (boolean = TRUE)
    SYNOPSIS: xxx
     

    turnoff_table_autocreate()
    SIGNATURE: boolean turnoff_table_autocreate ()
    SYNOPSIS: xxx
     

    reset()
    SIGNATURE: virtual int reset ()
    SYNOPSIS: xxx
     

    store_create()
    SIGNATURE: int store_create (int * = NULL)
    SYNOPSIS: creates the orthodox object's table (if it doesn't exist already)
    DESCRIPTION:
    rules: if a column has a "not null" or "unique", it can't have a "default" (but it can have a "check" and "default"). The ordering must be in this sequence: name, type, "not null", "unique", "default", "check". if the orthodox class has a parent class, this type of column definition will be added:
    "<tablename> int references <tablename> (id)"
    after the "owner_tabid" statement.
     

    store_add_null()
    SIGNATURE: int store_add_null (int * = NULL)
    SYNOPSIS: xxx
     

    store_add()
    SIGNATURE: virtual count_t store_add (int *pi = NULL, int recurse = 0)
    SYNOPSIS:
    adds the current object into the database. WARNING: do not set the 'recurse' value, this is for internal use only (and setting it to any non-default value will damage your database and void your warranty).
    PARAMETERS

  • pi: [output] error indicator variable. values:
    0: item instance successfully added;
    1: class name not set up
    2: could not add main record
    3: error checking / creating table; no d.b. access?
    4: no "id" field in data bag!
    5: no "subtables" in data bag
    6: error getting subtable name
    7: error getting sub-table list
    [n > 10]: see corresponding error code from dbbi_o::add()
  • RETURNS:
    [n > 0]: the id of the row just added;
    -1: error ocurred; see value in output variable 'pi'
     

    store_update()
    SIGNATURE: virtual int store_update (int *pi = NULL)
    SYNOPSIS:
    this routine is intended for objects that have been fetched from the database prior to this call. It builds a query based on the 'id' value. This field is provided automatically by the orthodox object. Each saved object has a unique 'id' value, so the update should always be able to find its object.
    DESCRIPTION:
    This subroutine has some optimization. It looks at fields whose values have changed. Only changed fields are included in the SQL update statement. Thus, given this:

      class MyItem : public orthodox_o
      {
        // class definition code
      };
      MyItem mio;
      mio.store_fetch("field0 = 'value0'");
      mio.put ("field0", "value0");
      mio.put ("field1", "value1", TRUE);
      mio.store_update();
    
    In this case, where the class 'MyItem' contains database fields "field0" and "field1", the sql statement will not include any reference to "field0" because the value has not changed: the value of "field0" for the record retrieved is "value0", and we have set the field to the same value. Because the value has not changed, the databag records this value as "not modified". During the set-up of the SQL UPDATE statement, the "where clause" is based on the "id" value (say it is 770). Since only changed record fields are included in the UPDATE statement, the SQL statement will look like this:
      UPDATE MyItem SET field1 = 'value1' WHERE id = 770;
    
    Notice that in the databag "put()" statemnt, we have set the 3rd parameter to TRUE (which is normally not included):
      mio.put ("field1", "value1", TRUE);
    
    This forces the databag to set the "is modified" flag. Normally, this flag is set only if the value has been changed. For example, if the current value of "field1" is "foobar" and we set it to "value2":
      mio.put ("field1", "value2");
    
    The value is changed, so the databag was modified, and subsequently, the field ("field1") will be included in the UPDATE statement. But if the value is "foobar", and we "change" it, like so:
      mio.put ("field1", "foobar");
    
    There is no change (and we did not force the "is modified" flag), so "field1" would not be included in the UPDATE statement.
    Thus, the application has some control on fields to be updated in the database. If you're not sure if the value of a field has changed, and you want to be absolutely certain that the database record has the current/new value, you can add the "TRUE" (3rd) parameter in the object's "put()" call prior to doing an update.
     

    store_fetch_byowner()
    SIGNATURE: virtual int store_fetch_byowner (int *pi = NULL)
    SYNOPSIS:
    This subroutine is really a wrapper around store_fetch(). If there is more than 1 record that satisfies the ownership criteria, the first one found (in the database) will get returned.
    PARAMETERS

  • pi: [output] error indicator variable. values:
    10: no "owner" field in the data bag
    11: no "owner_tabid" field in the data bag
    [all other values]: see "store_fetch()"
  •  

    store_fetch()
    SIGNATURE: virtual int store_fetch (const string_o &where = "", const string_o &ordby = "", int *pi = NULL)
    SYNOPSIS:
    get an item by ID number. This routine retrieves at most 1 item. You may specify a "where clause" (in parameter 'where'). If you do so and there is more than 1 object that matches the criteria, only the first object is retrieved.
    PARAMETERS

    • where: [optional] "where clause", passed directly to a SELECT [SQL] query. Do not include the initial "WHERE" keyword. If this string is empty, the object's ID will be used as the basis of the search. If you don't want to provide any where clause for an orthodox object, use store_nfetch() instead of this function.
    • ordby: [optional] "order by clause", passed directly to a SELECT [SQL] query. Do not include the initial "ORDER BY" keywords. May include "ASC" (for ascending) or "DESC" (ie, "descending").
    • pi: [output] error indicator variable. values:
      0: all ok, got the record;
      1: no such record
      2: object "class name" not initialized
      3: no "id" (an internal error!)
      4: "id" is not set in the object
      5: no "sub-tables" data bag found (internal error!)
      6: error in fetching the requested row
      8-9: other internal errors
      10: max [user-defined] limit exceeded
     

    store_nfetch()
    SIGNATURE: int store_nfetch (dbbi_o &dx, count_t &i, const count_t max_num, string_o &where_clause, const string_o &ordby = "", int *pi = NULL)
    SYNOPSIS:
    this member function fetches objects from the database where it is expected to be 2 or more objects that match the condition of the query.
    PARAMETERS

    • dx: An instance of a "dbbi" object ('dx'). This need not be initialized to anything. It serves as a cookie to the internals of store_nfetch().
    • i: this tracks the record number fetched. It must be initialized to 0 and should not be altered by the application as long as this function is to be called again.
    • max_num: this is the maximum number of records to fetch. Set this to 0 if all records are to be retrieved.
    • where_clause: a string containing a valid SQL "where clause". this is passed verbatim to the SQL database engine.
    • ordby: a string containing a valid SQL "order by clause". this is passed verbatim. Must not contain "order by" keywords.
    • pi: an error indicator [output] variable. values:
      0: successful fetch
      2: object class name not set
      3: object has no "id" field
      4: "id" field is empty (panic)
      5: internal error - no "orthodox_zone"
      9: internal error (in check_store())
      10: maximum number of rows hit (max_num)
    DESCRIPTION:
    this function is intended to be used inside a loop as so.
        person_o p;         // local work variable, "person"
        count_t i = 0;      // index, count # objects fetched
        dbbi_o dx;          // set up a low-level dbbi accessor
        string_o where_clause = "name like '%Jo%'";
        do
        {
            ie1 = p.store_nfetch (dx, i, 0, where_clause, &ie2);
            if (ie1) break;
            /* do things with 'p' */
        } while (1);
    
    It is absolutely critical that the counter ('i' in this case) be initialized to 0 prior to the first invocation of store_nfetch(). Otherwise, internal initialization will not occur and the routine will [erronously] fail. Also, do not modify it (such as incrementing)! store_nfetch() will automatically increment it.
     

    store_remove()
    SIGNATURE: virtual int store_remove (const string_o &wclause = "", int *pi = NULL)
    SYNOPSIS: removes 1 or more objects from the database
    PARAMETERS

    • pi: [output] error indicator variable.
    • wclause: string containing an optional "where clause", that is, a match criteria specifying what objects to remove. This string is passed verbatim to an SQL SELECT statement. If not specified, only the object whose ID matches that of the current object will be deleted.
     

    add_subtable()
    SIGNATURE: int add_subtable (const string_o &snam, int *pi = NULL)
    SYNOPSIS:
    this adds the database table name 'snam' to the internal list of subtables for the sub-class the object is representing. For example, the business class business_o has postal_address_o as a subtable element. The latter would be added to the former as a subtable via the call add_subtable("postal_address").
    DESCRIPTION:
    this adds 'snam' as a simple array element to the array databag within the object's internal [recursive] databag given by the path "orthodox_zone sub_tables".
     

    add_object()
    SIGNATURE: int add_object (const orthodox_o &o, int *pi = NULL)
    SYNOPSIS: This member function adds an instance of 'o' (which is of some type subclassed from orthodox_o) to the object.
    DESCRIPTION:
    This function should not be confused with add_subtable(), which is a "semi-private" function that operates on the data schema. This function is meant to be used by the application and affects the data itself, as opposed to the schema.
    Example:

    int ie0, ie1, ie2;
    phone_number_o pho;
    person_o hub;
    
    pho = "+1 (212) 555-1414"; ie0 = hub.set_value ("first_name", "Edward"); ie1 = hub.add_object(pho); // adds the phone # to Edward ie2 = hub.store_add(); // save Ed and his phone to DB

     

    set_table_with_schema()
    SIGNATURE: int set_table_with_schema (const string_o &s, dbbi_o &, int *pi)
    SYNOPSIS:
    fetches table schema from '_known_tableschemas' and sets it in dbbi_o. also updates '_known_tableschemas' if there is no scheme.
    TRAITS: this is a static function
     

    operator =()
    SIGNATURE: virtual orthodox_o &operator = (const rec_dbag_o &)
    SYNOPSIS: xxx
     

    copy_from_databag()
    SIGNATURE: int copy_from_databag (const rec_dbag_o &)
    SYNOPSIS:
    This function exists to complement and provide balance with the member function copy_to_databag(). This is not intended to be virtual, as it is simply a wrapper around the virtual function "operator = (rec_dbag_o)".
     

    copy_to_databag()
    SIGNATURE: int copy_to_databag (rec_dbag_o &unused)
    SYNOPSIS:
    if this function returns "1", then the data in the current object's bag is good to go as-is; else [if it returns 0], use the output bag.
     

    push_warning()
    SIGNATURE: int push_warning (int, int, const string_o &)
    SYNOPSIS: xxx
     

    extract_warning()
    SIGNATURE: int extract_warning (string_o &)
    SYNOPSIS: xxx
     

    clean_warnings()
    SIGNATURE: int clean_warnings ()
    SYNOPSIS: xxx
     

    has_subtable()
    SIGNATURE: int has_subtable (const string_o &, int * = NULL)
    SYNOPSIS: [[tells if the class has a subtable]]
     

    has_foreign_key()
    SIGNATURE: int has_foreign_key (const string_o &snam, string_o &sorce, string_o &dest, int *pi = NULL)
    SYNOPSIS: xxx
    PARAMETERS

  • pi: [output] error indicator variable. values:
    0: all ok
    1: not a valid object
    2: foreign key not found
  •  

    bad_reference()
    SIGNATURE: static const orthodox_o &bad_reference ()
    SYNOPSIS: xxx
     

    init_ortho()
    SIGNATURE: int init_ortho (const string_o &my_schema_bag, int *pi)
    SYNOPSIS:
    This routine should be called in the sub-classe's constructor (initializaton) process. The client-user passes in a big string that has the representation of a data bag that defines the table schema of the class to be initialized.
    PARAMETERS

    • my_schema_bag:
    • pi: an error indicator [output] variable. values:
      0: success
      1: current object's name matches that of input parameter 'my_schema_bag' (informational)
      zErr_NotSupported: parent object found! (handling of this is NOT CURRENTLY IMPLEMENTED) zErr_Memory_Exhausted: "pre_init()" failed - internal failure (PANIC), eg cannot allocate memory
    RETURNS:
    0: success
    -1: error ocurred; see value in output variable 'pi'
    TRAITS: this is a protected function
     

    init_ortho()
    SIGNATURE: int init_ortho (rec_dbag_o &)
    SYNOPSIS: xxx
    TRAITS: this is a protected function
     

    store_ifetch()
    SIGNATURE: int store_ifetch (dbbi_o &, count_t &, const count_t, string_o &, int *)
    SYNOPSIS: xxx
    TRAITS: this is a protected function
     

    check_store()
    SIGNATURE: int check_store ()
    SYNOPSIS: xxx
    TRAITS: this is a protected function
     

    add_with_id()
    SIGNATURE: int add_with_id ()
    SYNOPSIS: xxx
    TRAITS: this is a protected function
     

    merge_common_stuff()
    SIGNATURE: int merge_common_stuff (rec_dbag_o &, const rec_dbag_o &)
    SYNOPSIS: xxx
    TRAITS: this is a protected function
     

    reset_found_date()
    SIGNATURE: int reset_found_date ()
    SYNOPSIS: xxx
    TRAITS: this is a protected function
     

    full_rebuild()
    SIGNATURE: int full_rebuild ()
    SYNOPSIS: xxx
    TRAITS: this is a protected function. it is also inlined
     

    usage.
    [1] CREATING AN ORTHODOX SUBCLASS: here is [very briefly] a boilerplate example of the steps required to set up a working orthodox object:

    • include the header file for orthodox_o:
          #include "z_orthodox.h"
          
    • create a subclass from orthodox_o:
          class my_obj : public orthodox_o
          {
          public:
              my_obj();
          };
          
    • define the table schema. Put the schema in a string:
          static const char myobj_TP[] =
          "my_thing \
          ( \
              columns \
              [ \
                /* so on, full definition */ \
              ] \
          )";
          
    • create a singleton global pointer to a recursive data bag that employs the table schema:
          static rec_dbag_o *gp_myobj = NULL;
          
    • use the following lines inside your constructor (or have it be called by each constructor):
          my_obj::my_obj()
          {
              if (gp_myobj == NULL)
                  gp_myobj = new rec_dbag_o(myobj_TP);
              set_class_name ("my_thing");
              init_ortho (*(rec_dbag_o *) gp_myobj);
          }
          

    [2] FIND THE OWNER: given the ID of a simple, reparentable orthodox object which can be found in a database, determine the class type of its owner and retrieve the corresponding record. Examples of simple, reparentable

    note.
    The orthodox class is a foundation class for "business objects". It serves as a base class for many other objects. It may be likened to the NIH class library, where objects are sub-classed from a master class. Another analogy is Versant's (o-o) database methodology, where if a class is to be stored in a database, it must have one of Versant's classes as a parent class.

    The rather unusual name of this class goes back to James Coplien's purple book. There can be found a reference to the "orthodox canonical format". This format gives a foundation for how to write standardized classes in c++. The Z Directory's Orthodox class, in a loosely related fashion, provides for some standard operations for its child classes.

    An instance of this class is surprisingly devoid of contents.

    CONCURRENCY ISSUES: In order to implement concurrent access to a given "orthodox" table (via the DBBI object), it was found that (in a SQL Server / Win32 environment) manual control of the value of the "id" field was preventing obtaining true parallel processing. In a former implementation, the "id" field was of type integer - but not serial. A look-up is done to retrieve the highest database table, which is then incremented in the code. One reason for this manual control is to accommodate databases that have no 'serial' type. This value is then supplied to "sub-table" (or "child") items. For each child record, such as a person's phone number or address, the ID is used as a foreign key to its owner record.

    It was found that given a program that added records to a table running on the same machine as the database server, the process does not release access to the table. If another copy of the program is run on a remote machine at the same time, it never gets access to the table and every "add" operation failed. To remedy this situation, an optional serial-key facility was provided. In order to implement this as an additional feature (as opposed to a replacement), some additional plumbing needed to be added, such as adding "serial" to the "qual" field for the "id" row (in the list of table fields for all orthodox objects).

    warnings.
    The orthodox object is very complicated. It is best to focus on how to use it and its behaviour, rather on how its internals function.

    examples.
    This example pulls together all the individual instructions above into a complete, working sample. It sets up the database table "job_offer" and adds 1 row to it. Also, at the end, a loop is set up to fetch multiple job offers.

    #include "z_orthodox.h"
    
    static const char job_schema_TP[] =
    "job_offer \
    ( \
        is_equipment NO \
        is_code      NO \
    \
        columns \
        [ \
    <name   type    qual   nulls_ok is_uniq ref_table ref_column default check> \
    <posting_ID char \"12\"  NO       NO     \"\"    \"\"    \"\"    \"\"> \
    <url     varchar \"80\"  NO       NO     \"\"    \"\"    \"\"    \"\"> \
    <email_to varchar\"64\" YES       NO     \"\"    \"\"    \"\"    \"\"> \
    <posted_on varchar \"24\" NO      NO     \"\"    \"\"    \"\"    \"\"> \
    <title   varchar \"120\" YES      NO     \"\"    \"\"    \"\"    \"\"> \
    <area    varchar \"64\" YES       NO     \"\"    \"\"    \"\"    \"\"> \
    <raw_pay varchar \"64\" YES       NO     \"\"    \"\"    \"\"    \"\"> \
    <job_start   date \"\"  YES       NO     \"\"    \"\"    \"\"    \"\"> \
    <job_end     date \"\"  YES       NO     \"\"    \"\"    \"\"    \"\"> \
    <body       text \"\"    NO       NO     \"\"    \"\"    \"\"    \"\"> \
        ] \
       indexes \
        ( \
        ) \
        primary_key < posting_ID > \
        foreign_key \
        [ \
            <source_column dest_table dest_column> \
        ] \
    ) ";
    
    static rec_dbag_o *glop_jo = NULL;
    
    class job_offer : public orthodox_o
    {
    public:
        job_offer();
        ~job_offer() { }
    };
    
    job_offer::job_offer()
    {
        if (glop_jo == NULL)
            glop_jo = new rec_dbag_o(job_schema_TP);
    
        set_class_name ("job_offer");
        init_ortho (*(rec_dbag_o *) glop_jo);
    }
    
    void main()
    {
        int ie, ie2;
    
        // --configure the database parameters here--
        if (::z_open_database("localhost", "my_DB", "sa", "sa-pass", &ie))
            exit(0);
    
        if (!zdb_table_exists("job_offer", &ie))
        {
            job_offer x;
            ie = x.store_create();          // create the table
            if (ie) exit(1);
        }
    
        job_offer x, y;
    
        x.put ("posting_ID", "3547874120");
        x.put ("url",        "www.monster.com");
        x.put ("title",      "Build Engineer");
        x.put ("email_to",   "anybody@employer.com");
        x.put ("posted_on",  "Dec 10, 2012");
        x.put ("body",       "blah blah, foo bar, more text..");
        x.put ("area",        "bay area");
        x.put ("raw_pay",     "volunteer work");
    
        ie = x.store_add();                 // add object 'x' to the database
        if (ie < 0) exit(1);
    
        string_o s_pat = "posting_ID='3547874120'";
        ie = y.store_fetch(s_pat);          // now go retrieve it
        if (ie == 1)                        // 1 means no match
            std::cout << "Error! Failed to add the job offer record.\n";
        else
            std::cout << "Job Offer Saved. title = " << y.get("title", &ie) << "\n";
    
        // --get all job offers--
        dbbi_o dx;              // set up a low-level dbbi accessor
        s_pat = "posting_ID IS NOT NULL";
        count_t idx = 0;
        do
        {
            ie = y.store_nfetch(dx, idx, 0, s_pat, &ie2);
            if (ie)
                break;
            std::cout << idx << ": retrieved Job Offer: " << y.get("posting_ID") << "\n";
        } while (1);
    }
    

    history.

    ??? 07/05/1998: added list of objects global pool ("_p_known_tablebags")
    ??? 07/11/1998: added the list of table schemas global pool
    ??? 07/15/1998: major overhaul begun
    ??? 07/16/1998: orthodox class object takes shape
    ??? 07/18/1998: adding support for inheritance
    ??? 11/21/1998: all data-bags loaded from "store_fetch()" get trimmed
    Wed 07/22/1998: added check to see if name already in add_subtable()
    ??? 08/24/1998: created checkset_ortho_dbag() (an optimization)
    ??? 12/05/1998: make it a "vlist", not a "list"
    ??? 12/13/2001: changed "_p_known_tablebags" on "_known_tablebags"
    Thu 05/30/2002: new #define naming conventions ("zos_", "zcc_")
    Wed 06/25/2002: base class changed, rec_dbag_o -> dbtable_o {--AG}
    ??? 08/??/2002: "dbtable" class object now handles foreign key ref's
    Tue 10/08/2002: added index generation re. 'is_equipment' [--AG]