DSREP adaptor: database


This is the database DSREP adaptor for the wftk. Instead of saving the datasheet in a filesystem directory, it allows wftk to store it in a database. I'm not entirely sure how this will affect our caching policy, as changes to the record by remote users will not be immediately obvious in our local copy. So this will require some thought. As always, if you have already done this thought, tell me.

This is the first adaptor which itself will use an adaptor (the taskindex adaptor). Woo.
 
#include <stdio.h>
#include <stdarg.h>
#include "xmlapi.h"
#include "../wftk_session.h"
#include "../wftk_internals.h"
The adaptor_info structure is used to pass adaptor info (duh) back to the config module when it's building an adaptor instance. Here's what it contains:
 
static char *names[] = 
{
   "init",
   "free",
   "info",
   "new",
   "load",
   "save",
   "delete",
   "archive"
};

XML * DSREP_database_init (WFTK_ADAPTOR * ad, va_list args);
XML * DSREP_database_free (WFTK_ADAPTOR * ad, va_list args);
XML * DSREP_database_info (WFTK_ADAPTOR * ad, va_list args);
XML * DSREP_database_new (WFTK_ADAPTOR * ad, va_list args);
XML * DSREP_database_load (WFTK_ADAPTOR * ad, va_list args);
XML * DSREP_database_save (WFTK_ADAPTOR * ad, va_list args);
XML * DSREP_database_delete (WFTK_ADAPTOR * ad, va_list args);
XML * DSREP_database_archive (WFTK_ADAPTOR * ad, va_list args);

static WFTK_API_FUNC vtab[] = 
{
   DSREP_database_init,
   DSREP_database_free,
   DSREP_database_info,
   DSREP_database_new,
   DSREP_database_load,
   DSREP_database_save,
   DSREP_database_delete,
   DSREP_database_archive
};

static struct wftk_adaptor_info _DSREP_database_info =
{
   8,
   names,
   vtab
};
Cool. So here's the incredibly complex function which returns a pointer to that:
 
struct wftk_adaptor_info * DSREP_database_get_info ()
{
   return & _DSREP_database_info;
}
So here we are in our second generation of adaptors. The DSREP_database adaptor is the first adaptor which will itself use an adaptor (the adaptor it uses will of course be determined by the parameter passed to it.) That's cool. The adaptor used is stashed in the binary handle of the DSREP_database adaptor. The DSREP_database adaptor should be completely database-agnostic, working with any database adaptor. Here goes.
 
XML * DSREP_database_init (WFTK_ADAPTOR * ad, va_list args) {
   const char * parms;
   WFTK_ADAPTOR * db_ad;

   parms = xml_attrval (ad->parms, "parm");
   if (!*parms) parms = wftk_config_get_value (ad->session, "dsrep.database.db");

   db_ad = wftk_get_adaptor (ad->session, TASKINDEX, parms);
   if (!db_ad) {
      xml_setf (ad->parms, "error", "Unable to initialize database %s", parms);
      return NULL;
   }
   ad->bindata = (void *) db_ad;
   xml_setf (ad->parms, "spec", "database:%s", parms);

   return (XML *) 0;
}
That wasn't so hard, was it? Well, freeing up the adaptor is much easier:
 
XML * DSREP_database_free (WFTK_ADAPTOR * ad, va_list args) {
   WFTK_ADAPTOR * db_ad = (WFTK_ADAPTOR *) ad->bindata;
   wftk_free_adaptor (ad->session, db_ad);
   return (XML *) 0;
}
Next up is the info call, which builds and returns a little XML telling the caller about the adaptor. If the adaptor itself is NULL, then it just returns info about the installed adaptor handler; otherwise it's free to elaborate on the adaptor instance.
 
XML * DSREP_database_info (WFTK_ADAPTOR * ad, va_list args) {
   XML * info;

   info = xml_create ("info");
   xml_set (info, "type", "dsrep");
   xml_set (info, "name", "database");
   xml_set (info, "ver", "1.0.0");
   xml_set (info, "compiled", __DATE__ " " __TIME__);
   xml_set (info, "author", "Michael Roberts");
   xml_set (info, "contact", "wftk@vivtek.com");
   xml_set (info, "extra_functions", "0");

   return (info);
}

So. Creation of a new datasheet, for the database adaptor, is the insertion of a new process record. If the ID of the datasheet is specified, then the procnew may fail. If it's not specified, the procnew will generate one (or it will allow the database to generate one) that's guaranteed unique.
 
XML * DSREP_database_new  (WFTK_ADAPTOR * ad, va_list args)
{
   WFTK_ADAPTOR *db_ad = (WFTK_ADAPTOR *) ad->bindata;
   char * id = (char *) 0;
   FILE * file;
   XML * ret = xml_create ("datasheet");

   if (args) id = va_arg (args, char *);
   if (id) xml_set (ret, "id", id);
   xml_set (ret, "noindex", "yes"); /* Tells wftk not to tell the taskindex about this proc directly. */

   wftk_call_adaptor (db_ad, "procnew", ret);
   if (*xml_attrval (db_ad->parms, "error")) {
      if (id) {
         xml_setf (ad->parms, "error", "Process '%s' already exists.", xml_attrval (ret, "id"));
         xml_set (ret, "id", "");
      } else {
         xml_setf (ad->parms, "error", xml_attrval (db_ad->parms, "error"));
      }
   } else {
      wftk_call_adaptor (db_ad, "xmlput", "process", "id", xml_attrval (ret, "id"), "datasheet", ret);
   }
   return ret;
}
Loading a datasheet is easy. We just have the adaptor do a procget on the ID, and an xmlget on the datasheet column. We roll the results of the procget into the datasheet, marking the fields thus written as special database fields using the datastore adaptor "currecord". Writes to the store will be written directly to the database, and the fields will be removed from the datasheet before it's written back to the process row.
 
XML * DSREP_database_load (WFTK_ADAPTOR * ad, va_list args) {
   WFTK_ADAPTOR *db_ad = (WFTK_ADAPTOR *) ad->bindata;
   XML * proc;
   XML * datasheet;
   XML * data;
   XML_ATTR * field;
   char *id = (char *) 0;

   if (args) id = va_arg (args, char *);
   if (!id) {
      xml_set (ad->parms, "error", "No ID given.");
      return (XML *) 0;
   }

   proc = wftk_call_adaptor (db_ad, "procget", id);

   if (!proc) {
      xml_setf (ad->parms, "error", "Process '%s' not found.", id);
      return (XML *) 0;
   }

   datasheet = wftk_call_adaptor (db_ad, "xmlget", "process", "id", xml_attrval (proc, "id"), "datasheet");

   if (!datasheet) {
      datasheet = xml_create ("datasheet");
      xml_set (datasheet, "id", xml_attrval (proc, "id"));
   }
   xml_set (datasheet, "noindex", "yes");

   field = xml_attrfirst (proc);
   while (field) {
      if (strcmp (xml_attrname (field), "id") &&
          strcmp (xml_attrname (field), "status") &&
          strcmp (xml_attrname (field), "title") &&
          strcmp (xml_attrname (field), "user")) {
         data = xml_create ("data");
         xml_set (data, "id", xml_attrname (field));
         xml_set (data, "value", xml_attrvalue (field));
         xml_setf (data, "storage", "currecord:%s", xml_attrval (db_ad->parms, "spec"));
         xml_append (datasheet, data);
      }
      field = xml_attrnext (field);
   }
   xml_free (proc);
   return datasheet;

}
Saving is not a terribly elegant thing; it requires two queries. First, we call the procput on the database adaptor to make sure the row is there; this also updates the basic information like title and status. Second, we modify the datasheet to exclude any data fields which are actually columns in the database row, and we write the modified datasheet using xmlput. Finally, we restore the data fields. (You might think we'd need to write all the column values, but I'm assuming that the currecord datastore adaptor will take care of that each time one is actually changed.)
 
XML * DSREP_database_save (WFTK_ADAPTOR * ad, va_list args) {
   WFTK_ADAPTOR *db_ad = (WFTK_ADAPTOR *) ad->bindata;
   XML  * ds = (XML *) 0;
   XML * fields;
   XML * field;

   if (args) ds = va_arg (args, XML *);
   if (!ds) {
      xml_set (ad->parms, "error", "No datasheet given.");
      return (XML *) 0;
   }

   fields = xml_create ("fields");
   wftk_call_adaptor (db_ad, "procput", ds);

   field = xml_firstelem (ds);
   while (field) {
      if (xml_is (field, "data") && !strncmp (xml_attrval (field, "storage"), "currecord:", 10)) {
         xml_append (fields, xml_copy (field));
         xml_delete (field);
         field = xml_firstelem (ds);
      }
      field = xml_nextelem (field);
   }

   wftk_call_adaptor (db_ad, "xmlput", "process", "id", xml_attrval (ds, "id"), "datasheet", ds);

   field = xml_firstelem (field);
   while (field) {
      xml_append (ds, xml_copy (field));
      field = xml_nextelem (field);
   }
   xml_free (fields);

   return ds;
}
Then deletion. Deletion doesn't involve XML manipulation, so it's absurdly simple.
 
XML * DSREP_database_delete (WFTK_ADAPTOR * ad, va_list args) {
   WFTK_ADAPTOR *db_ad = (WFTK_ADAPTOR *) ad->bindata;
   char * id = (char *) 0;

   if (args) id = va_arg (args, char *);
   if (!id) {
      xml_set (ad->parms, "error", "No ID given.");
      return (XML *) 0;
   }

   wftk_call_adaptor (db_ad, "procdel", id);
   return (XML *) 0;
}
And last, we have an archive function, but as I don't know yet how to handle it, I'm not implementing it.
 
XML * DSREP_database_archive (WFTK_ADAPTOR * ad, va_list args) { return (XML *) NULL; }

This code and documentation are released under the terms of the GNU license. They are additionally copyright (c) 2000, Vivtek. All rights reserved except those explicitly granted under the terms of the GNU license. This presentation was prepared with LPML. Try literate programming. You'll like it.