374e65b5e68e635c5ee05c6e58cdbd5e56c62e14
Merge parents abe5295 40187ad
kent
  Wed May 18 15:49:58 2011 -0700
Refreshing from Angie's tree.
diff --cc src/hg/lib/mdb.c
index c1b60c8,08e6004..ecbf8b8
--- src/hg/lib/mdb.c
+++ src/hg/lib/mdb.c
@@@ -1,3403 -1,3401 +1,3405 @@@
  /* mdb.c was originally generated by the autoSql program, which also
   * generated mdb.h and mdb.sql.  This module links the database and
   * the RAM representation of objects. */
  
  #include "common.h"
  #include "linefile.h"
  #include "dystring.h"
  #include "jksql.h"
  #include "hdb.h"
  #include "cv.h"
  #include "mdb.h"
  #include "encode/encodeExp.h"
  #include <regex.h>
  
  static char const rcsid[] = "$Id: mdb.c,v 1.8 2010/06/11 17:11:28 tdreszer Exp $";
  
  void mdbStaticLoad(char **row, struct mdb *ret)
  /* Load a row from mdb table into ret.  The contents of ret will
   * be replaced at the next call to this function. */
  {
  
  ret->obj = row[0];
  ret->var = row[1];
  ret->val = row[2];
  }
  
  struct mdb *mdbLoadByQuery(struct sqlConnection *conn, char *query)
  /* Load all mdb from table that satisfy the query given.
   * Where query is of the form 'select * from example where something=something'
   * or 'select example.* from example, anotherTable where example.something =
   * anotherTable.something'.
   * Dispose of this with mdbFreeList(). */
  {
  struct mdb *list = NULL, *el;
  struct sqlResult *sr;
  char **row;
  
  sr = sqlGetResult(conn, query);
  while ((row = sqlNextRow(sr)) != NULL)
      {
      el = mdbLoad(row);
      slAddHead(&list, el);
      }
  slReverse(&list);
  sqlFreeResult(&sr);
  return list;
  }
  
  void mdbSaveToDb(struct sqlConnection *conn, struct mdb *el, char *tableName, int updateSize)
  /* Save mdb as a row to the table specified by tableName.
   * As blob fields may be arbitrary size updateSize specifies the approx size
   * of a string that would contain the entire query. Arrays of native types are
   * converted to comma separated strings and loaded as such, User defined types are
   * inserted as NULL. Note that strings must be escaped to allow insertion into the database.
   * For example "autosql's features include" --> "autosql\'s features include"
   * If worried about this use mdbSaveToDbEscaped() */
  {
  struct dyString *update = newDyString(updateSize);
  dyStringPrintf(update, "insert into %s set obj='%s', var='%s', val='%s'",
  	tableName,  el->obj,  el->var,  el->val);
  sqlUpdate(conn, update->string);
  freeDyString(&update);
  }
  
  void mdbSaveToDbEscaped(struct sqlConnection *conn, struct mdb *el, char *tableName, int updateSize)
  /* Save mdb as a row to the table specified by tableName.
   * As blob fields may be arbitrary size updateSize specifies the approx size.
   * of a string that would contain the entire query. Automatically
   * escapes all simple strings (not arrays of string) but may be slower than mdbSaveToDb().
   * For example automatically copies and converts:
   * "autosql's features include" --> "autosql\'s features include"
   * before inserting into database. */
  {
  struct dyString *update = newDyString(updateSize);
  char  *obj, *var, *val;
  obj = sqlEscapeString(el->obj);
  var = sqlEscapeString(el->var);
  val = sqlEscapeString(el->val);
  
  dyStringPrintf(update, "insert into %s set obj='%s', var='%s', val='%s'",
  	tableName,  obj,  var,  val);
  sqlUpdate(conn, update->string);
  freeDyString(&update);
  freez(&obj);
  freez(&var);
  freez(&val);
  }
  
  struct mdb *mdbLoad(char **row)
  /* Load a mdb from row fetched with select * from mdb
   * from database.  Dispose of this with mdbFree(). */
  {
  struct mdb *ret;
  
  AllocVar(ret);
  ret->obj = cloneString(row[0]);
  ret->var = cloneString(row[1]);
  ret->val = cloneString(row[2]);
  return ret;
  }
  
  struct mdb *mdbLoadAll(char *fileName)
  /* Load all mdb from a whitespace-separated file.
   * Dispose of this with mdbFreeList(). */
  {
  struct mdb *list = NULL, *el;
  struct lineFile *lf = lineFileOpen(fileName, TRUE);
  char *row[4];
  
  while (lineFileRow(lf, row))
      {
      el = mdbLoad(row);
      slAddHead(&list, el);
      }
  lineFileClose(&lf);
  slReverse(&list);
  return list;
  }
  
  struct mdb *mdbLoadAllByChar(char *fileName, char chopper)
  /* Load all mdb from a chopper separated file.
   * Dispose of this with mdbFreeList(). */
  {
  struct mdb *list = NULL, *el;
  struct lineFile *lf = lineFileOpen(fileName, TRUE);
  char *row[4];
  
  while (lineFileNextCharRow(lf, chopper, row, ArraySize(row)))
      {
      el = mdbLoad(row);
      slAddHead(&list, el);
      }
  lineFileClose(&lf);
  slReverse(&list);
  return list;
  }
  
  struct mdb *mdbCommaIn(char **pS, struct mdb *ret)
  /* Create a mdb out of a comma separated string.
   * This will fill in ret if non-null, otherwise will
   * return a new mdb */
  {
  char *s = *pS;
  
  if (ret == NULL)
      AllocVar(ret);
  ret->obj = sqlStringComma(&s);
  ret->var = sqlStringComma(&s);
  ret->val = sqlStringComma(&s);
  *pS = s;
  return ret;
  }
  
  void mdbFree(struct mdb **pEl)
  /* Free a single dynamically allocated mdb such as created
   * with mdbLoad(). */
  {
  struct mdb *el;
  
  if ((el = *pEl) == NULL) return;
  freeMem(el->obj);
  freeMem(el->var);
  freeMem(el->val);
  freez(pEl);
  }
  
  void mdbFreeList(struct mdb **pList)
  /* Free a list of dynamically allocated mdb's */
  {
  struct mdb *el, *next;
  
  for (el = *pList; el != NULL; el = next)
      {
      next = el->next;
      mdbFree(&el);
      }
  *pList = NULL;
  }
  
  void mdbOutput(struct mdb *el, FILE *f, char sep, char lastSep)
  /* Print out mdb.  Separate fields with sep. Follow last field with lastSep. */
  {
  if (sep == ',') fputc('"',f);
  fprintf(f, "%s", el->obj);
  if (sep == ',') fputc('"',f);
  fputc(sep,f);
  if (sep == ',') fputc('"',f);
  fprintf(f, "%s", el->var);
  if (sep == ',') fputc('"',f);
  fputc(sep,f);
  if (sep == ',') fputc('"',f);
  fprintf(f, "%s", el->val);
  if (sep == ',') fputc('"',f);
  fputc(lastSep,f);
  }
  
  void mdbJsonOutput(struct mdb *el, FILE *f)
  /* Print out mdb in JSON format. */
  {
  fputc('{',f);
  fputc('"',f);
  fprintf(f,"obj");
  fputc('"',f);
  fputc(':',f);
  fputc('"',f);
  fprintf(f, "%s", el->obj);
  fputc('"',f);
  fputc(',',f);
  fputc('"',f);
  fprintf(f,"var");
  fputc('"',f);
  fputc(':',f);
  fputc('"',f);
  fprintf(f, "%s", el->var);
  fputc('"',f);
  fputc(',',f);
  fputc('"',f);
  fprintf(f,"val");
  fputc('"',f);
  fputc(':',f);
  fputc('"',f);
  fprintf(f, "%s", el->val);
  fputc('"',f);
  fputc('}',f);
  }
  
  /* -------------------------------- End autoSql Generated Code -------------------------------- */
  
  
  #include "ra.h"
  #include "hgConfig.h"
  #include "obscure.h"
  
  #define MDB_METADATA_KEY  "metadata"
  #define MDB_METAOBJ_RAKEY "metaObject"
  #define MDB_METAVAR_RAKEY "metaVariable"
  
  // ------- (static) convert from autoSql -------
  static void mdbLeafObjFree(struct mdbLeafObj **leafObjPtr)
  // Frees a single mdbVar struct
  {
      freeMem((*leafObjPtr)->obj);
      freez(leafObjPtr);
  }
  
  static void mdbLimbValFree(struct mdbLimbVal **limbValPtr)
  // Frees a single mdbVar struct
  {
  struct mdbLimbVal *limbVal = *limbValPtr;
  
      // Free hash first (shared memory)
      hashFree(&(limbVal->objHash));
  
      struct mdbLeafObj *leafObj = NULL;
      while((leafObj = slPopHead(&(limbVal->objs))) != NULL)
          mdbLeafObjFree(&leafObj);
  
      freeMem(limbVal->val);
      freez(limbValPtr);
  }
  
  static struct mdbVar *mdbVarNew(char *var, void *val)
  // Creates a new mdbVar and adds it onto the head of the list
  {
  struct mdbVar *mdbVar;
  AllocVar(mdbVar);
  mdbVar->var = cloneString(var);
  mdbVar->val = cloneString(val);
  return mdbVar;
  }
  
  static struct mdbVar *mdbVarAdd(struct mdbVar **pMdbVars, char *var, void *val)
  // Creates a new mdbVar and adds it onto the head of the list
  {
  struct mdbVar *mdbVar = mdbVarNew(var,val);
  slAddHead(pMdbVars, mdbVar);
  return mdbVar;
  }
  
  static struct mdbObj *mdbObjsLoadFromMemory(struct mdb **mdbPtr,boolean buildHashes)
  // Load all mdbObjs from in memory mdb struct, cannibalize strings.  Expects sorted order.
  {
  struct mdbObj *mdbObj  = NULL;
  struct mdbObj *mdbObjs = NULL;
  struct mdbVar *mdbVar;
  struct mdb *thisRow;
  while((thisRow = slPopHead(mdbPtr)) != NULL)
      {
      if (mdbObj == NULL || differentString(thisRow->obj,mdbObj->obj) )
          {
          // Finish last object before starting next!
          if(mdbObj!= NULL)
              slReverse(&(mdbObjs->vars));
          // Start new object
          AllocVar(mdbObj);
          mdbObj->obj     = thisRow->obj;
          if ( buildHashes )
              mdbObj->varHash = hashNew(0);
          slAddHead(&mdbObjs,mdbObj);
          }
      else
          {
          freeMem(thisRow->obj);  // Already got this from prev row
          }
  
      AllocVar(mdbVar);
      mdbVar->var     = thisRow->var;
      mdbVar->val     = thisRow->val;
      slAddHead(&(mdbObj->vars),mdbVar);
      if ( buildHashes )
          hashAddUnique(mdbObj->varHash, mdbVar->var, mdbVar); // pointer to struct to resolve type
  
      freeMem(thisRow);
      }
  
  // Finish very last object
  if(mdbObjs && mdbObjs->vars)
      slReverse(&(mdbObjs->vars));
  if(mdbObjs)
      slReverse(&mdbObjs);
  
  return mdbObjs;
  }
  
  static struct mdbByVar *mdbByVarsLoadFromMemory(struct mdb **mdbPtr,boolean buildHashes)
  // Load all mdbVars from in memorys mdb struct, cannibalize strings.  Expects sorted order.
  {
  struct mdbByVar *rootVars = NULL;
  struct mdbByVar *rootVar  = NULL;
  struct mdbLimbVal *limbVal  = NULL;
  struct mdbLeafObj *leafObj;
  struct mdb *thisRow;
  while((thisRow = slPopHead(mdbPtr)) != NULL)
      {
      // Start at root
      if (rootVar == NULL || differentString(thisRow->var,rootVar->var) )
          {
          // Finish last var before starting next!
          if(rootVars && rootVars->vals && rootVars->vals->objs)
              slReverse(&(rootVars->vals->objs));
          if(rootVars && rootVars->vals)
              slReverse(&(rootVars->vals));
          // Start new var
          AllocVar(rootVar);
          limbVal = NULL;  // Very important!
          rootVar->var     = thisRow->var;
          if ( buildHashes )
              rootVar->valHash = hashNew(0);
          slAddHead(&rootVars,rootVar);
          }
      else
          {
          freeMem(thisRow->var);  // Already got this from prev row
          }
  
      // Continue with limb
      if (limbVal == NULL || differentString(thisRow->val,limbVal->val) )
          {
          // Finish last val before starting next!
          if(limbVal != NULL && limbVal->objs != NULL)
              slReverse(&(limbVal->objs));
          // Start new val
          AllocVar(limbVal);
          limbVal->val     = thisRow->val;
          if ( buildHashes )
              {
              hashAddUnique(rootVar->valHash, limbVal->val, limbVal); // Pointer to struct to get to objHash
              limbVal->objHash = hashNew(0);
              }
          slAddHead(&(rootVar->vals),limbVal);
          }
      else
          freeMem(thisRow->val);  // Already got this from prev row
  
      // End with leaf
      AllocVar(leafObj);
      leafObj->obj     = thisRow->obj;
      if ( buildHashes )
          hashAddUnique(limbVal->objHash, leafObj->obj, leafObj); // Pointer to struct to resolve type!
      slAddHead(&(limbVal->objs),leafObj);
  
      freeMem(thisRow);
      }
  
  // Finish very last object
  if(rootVars && rootVars->vals && rootVars->vals->objs)
      slReverse(&(rootVars->vals->objs));
  if(rootVars && rootVars->vals)
      slReverse(&(rootVars->vals));
  if(rootVars && rootVars->vals)
      slReverse(&rootVars);
  
  return rootVars;
  }
  
  
  static int mdbObjCRC(struct mdbObj *mdbObjs)
  // returns a summ of all individual CRC values of all metObj strings
  {
  int crc = 0;
  struct mdbObj *mdbObj = NULL;
  for(mdbObj=mdbObjs;mdbObj!=NULL;mdbObj=mdbObj->next)
      {
      if(mdbObj->obj != NULL)
          crc += hashCrc(mdbObj->obj);
  
      struct mdbVar *mdbVar = NULL;
      for(mdbVar=mdbObj->vars;mdbVar!=NULL;mdbVar=mdbVar->next)
          {
          if(mdbVar->var != NULL)
              crc += hashCrc(mdbVar->var);
          if(mdbVar->val != NULL)
              crc += hashCrc(mdbVar->val);
          }
      }
  
  return crc;
  }
  
  // -------------- Sort primitives --------------
  int mdbObjCmp(const void *va, const void *vb)
  /* Compare to sort on label. */
  {
  const struct mdbObj *a = *((struct mdbObj **)va);
  const struct mdbObj *b = *((struct mdbObj **)vb);
  return strcasecmp(a->obj, b->obj);
  }
  
  int mdbVarCmp(const void *va, const void *vb)
  /* Compare to sort on label. */
  {
  const struct mdbVar *a = *((struct mdbVar **)va);
  const struct mdbVar *b = *((struct mdbVar **)vb);
  return strcasecmp(a->var, b->var);
  }
  
  
  // ------ Parsing lines ------
  
  struct mdbObj *mdbObjAddVarPairs(struct mdbObj *oldObj,char *varPairs)
  // Parses line of var=val pairs adding to a mdbObj.  Creates mdbObj if NULL
  {
  struct mdbObj *mdbObj = oldObj;
  struct mdbVar *mdbVar;
  char *cloneVars = cloneString(varPairs);
  
      // initial chop and determine if this looks like metadata
      int count = chopByWhiteRespectDoubleQuotes(cloneVars,NULL,0);
      char **words = needMem(sizeof(char *) * count);
      count = chopByWhiteRespectDoubleQuotes(cloneVars,words,count);
      if(count < 1 || words[0] == NULL)
          {
          errAbort("This is not formatted var=val pairs:\n\t%s\n",varPairs);
          }
  
      verbose(3, "mdbObjAddVarPairs() word count:%d\n\t%s\n",count,varPairs);
  
      if(mdbObj == NULL)
          AllocVar(mdbObj);
      if(mdbObj->varHash == NULL)
          mdbObj->varHash = hashNew(0);
  
      int ix;
      for(ix = 0;ix<count;ix++)
          {
          if(*words[ix] == '#')
              break;
          if(strchr(words[ix], '=') == NULL)
              errAbort("This is not formatted var=val pairs: '%s'\n\t%s\n",words[ix],varPairs);
  
          AllocVar(mdbVar);
          mdbVar->var = cloneNextWordByDelimiter(&(words[ix]),'=');
          mdbVar->val = cloneString(words[ix]);
          verbose(3, "mdbObjAddVarPairs() var=val: %s=%s\n",mdbVar->var,mdbVar->val);
          struct mdbVar *oldVar = (struct mdbVar *)hashFindVal(mdbObj->varHash, mdbVar->var);
          if(oldVar)
              {
              verbose(1, "The same variable appears twice: %s=%s and %s=%s.  Ignoring second value.\n\t%s\n",
                  oldVar->var,oldVar->val,mdbVar->var,mdbVar->val,varPairs);
              mdbVarFree(&mdbVar);
              }
          else
              {
              hashAdd(mdbObj->varHash, mdbVar->var, mdbVar); // pointer to struct to resolve type
              slAddHead(&(mdbObj->vars),mdbVar);
              }
          }
      freeMem(words);
      freeMem(cloneVars);
  
      // Special for old style ENCODE metadata
  #define ENCODE_ALN  "Alignments"
  #define ENCODE_RSIG "RawSignal"
      if(mdbObj->obj == NULL)
          {
          char * tableName = NULL;
          char * fileName = NULL;
          for(mdbVar  = mdbObj->vars;
              mdbVar != NULL && (tableName == NULL || fileName == NULL);
              mdbVar  = mdbVar->next)
              {
              if(sameString(mdbVar->var,MDB_VAR_TABLENAME))
                  tableName = mdbVar->val;
              else if(sameString(mdbVar->var,MDB_VAR_FILENAME))
                  fileName = mdbVar->val;
              }
  
          mdbVar = NULL; // assertably so, but this is conditioanally created below
          if(tableName != NULL)
              {
              verbose(3, "%s:%s\n",MDB_VAR_TABLENAME,tableName);
              if(fileName == NULL || startsWithWordByDelimiter(tableName,'.',fileName))
                  {
                  mdbObj->obj     = cloneString(tableName);
                  AllocVar(mdbVar);
                  mdbVar->var = cloneString(MDB_OBJ_TYPE);
                  mdbVar->val = cloneString(MDB_OBJ_TYPE_TABLE);
                  }
              else if(stringIn(ENCODE_ALN,fileName) && stringIn(ENCODE_RSIG,tableName))// Messier case where the file has "Alignment" but the table has "RawSignal"
                  {
                  char *tmpFilName = cloneString(fileName);
                  strSwapStrs(tmpFilName, strlen(tmpFilName),ENCODE_ALN, ENCODE_RSIG);
                  if(startsWithWordByDelimiter(tableName,'.',tmpFilName))
                      {
                      mdbObj->obj     = cloneString(tableName);
                      AllocVar(mdbVar);
                      mdbVar->var = cloneString(MDB_OBJ_TYPE);
                      mdbVar->val = cloneString(MDB_OBJ_TYPE_TABLE);
                      }
                  freeMem(tmpFilName);
                  }
              }
          else if(fileName != NULL)
              {
              verbose(3, "%s:%s\n",MDB_VAR_FILENAME,fileName);
              // NOTE: that the file object is the root of the name, so both file.fastq.gz and file.fastq are same obj!
              mdbObj->obj     = cloneFirstWordByDelimiter(fileName,'.');
              AllocVar(mdbVar);
              mdbVar->var = cloneString(MDB_OBJ_TYPE);
              mdbVar->val = cloneString(MDB_OBJ_TYPE_FILE);
              }
  
          if(mdbVar != NULL) // Just determined an objType
              {
              verbose(3, "mdbObjAddVarPairs() var=val: %s=%s\n",mdbVar->var,mdbVar->val);
              struct mdbVar *oldVar = (struct mdbVar *)hashFindVal(mdbObj->varHash, mdbVar->var);
              if(oldVar)
                  mdbVarFree(&mdbVar);
              else
                  {
                  hashAdd(mdbObj->varHash, mdbVar->var, mdbVar); // pointer to struct to resolve type
                  slAddHead(&(mdbObj->vars),mdbVar);
                  }
              }
          }
  
  if(mdbObj->obj == NULL) // NOTE: Should this be a hard error!
      errAbort("No obj found. This is not properly formatted metadata:\n\t%s\n",varPairs);
  
      //slReverse(&(mdbObj->vars)); Could have added vars so sort instead
      slSort(&(mdbObj->vars),&mdbVarCmp); // Should be in determined order
      mdbVar = (struct mdbVar *)hashFindVal(mdbObj->varHash, MDB_OBJ_TYPE);
      if(mdbVar == NULL)
          mdbVar = mdbObj->vars;
      verbose(3, "mdbObjAddVarPairs() obj=%s %s=%s\n",
      mdbObj->obj, mdbVar->var,mdbVar->val);
  return mdbObj;
  }
  
  struct mdbObj *metadataLineParse(char *line)
  /* Parses a single formatted metadata line into mdbObj for updates or queries. */
  {
  char *fromTheTop = line;
  char*nibbledWord = cloneNextWordByDelimiter(&line,' ');
  if(nibbledWord == NULL || differentWord(nibbledWord,MDB_METADATA_KEY))
      errAbort("This is not a formatted metadata line:\n\t%s\n",fromTheTop);
  freeMem(nibbledWord);
  
  struct mdbObj *mdbObj = NULL;
  char*varPairs = line;
  nibbledWord = cloneNextWordByDelimiter(&line,' ');;
  if(nibbledWord == NULL)
      errAbort("This is not a formatted metadata line:\n\t%s\n",fromTheTop);
  if(strchr(nibbledWord, '=') == NULL) // If this is not a var=val then it should be obj
      {
      AllocVar(mdbObj);
      mdbObj->obj = nibbledWord;
      verbose(3, "metadataLineParse() %s=%s\n",MDB_OBJ,mdbObj->obj);
      varPairs = line;
      while(strlen(line) > 0)
          {
          nibbledWord = cloneNextWordByDelimiter(&line,' ');;
          if(nibbledWord == NULL)
              errAbort("This is not a formatted metadata line:\n\t%s\n",fromTheTop);
          if(*nibbledWord == '#' || strchr(nibbledWord, '=') != NULL) // IS commnet OR start of var=val pairs
              break;
  
          if(sameWord(nibbledWord,"delete"))
              mdbObj->deleteThis = TRUE;
          else
              errAbort("This is not a formatted metadata line:\n\t%s\n",fromTheTop);
          varPairs = line;
          freeMem(nibbledWord);
          }
      }
  if(varPairs != NULL && strlen(varPairs) > 0 && *varPairs != '#')
          mdbObj = mdbObjAddVarPairs(mdbObj,varPairs);
  else if(mdbObj->deleteThis == FALSE)
      errAbort("This is not a formatted metadata line:\n\t%s\n",fromTheTop);
  return mdbObj;
  }
  
  struct mdbByVar *mdbByVarsLineParse(char *line)
  /* Parses a line of "var1=val1 var2=val2 into a mdbByVar object for queries. */
  {
  int thisWord = 0;
  struct mdbByVar   *mdbByVars = NULL;
  struct mdbByVar   *rootVar = NULL;
  struct mdbLimbVal *limbVal = NULL;
  char *cloneLine = cloneString(line);
  struct hash* varHash;     // There must not be multiple occurrances of the same var
  
      // initial chop and determine if this looks like metadata
      int count = chopByWhiteRespectDoubleQuotes(cloneLine,NULL,0);
      char **words = needMem(sizeof(char *) * count);
      count = chopByWhiteRespectDoubleQuotes(cloneLine,words,count);
  
      verbose(3, "mdbByVarsLineParse() word count:%d\n\t%s\n",count,line);
      // Get obj and figure out if this is a delete line
      varHash = hashNew(0);
  
      // All words are expected to be var=val pairs!
      for (thisWord=0; thisWord<count; thisWord++)
          {
          if (strchr(words[thisWord], '=') == NULL)
              errAbort("Expected '%s=%s' but found '%s'.  This is not properly formatted metadata:\n\t%s\n",MDB_VAR,MDB_VAL,words[thisWord],line);
  
          // Set up var struct from 1st half of pair
          AllocVar(rootVar);
          rootVar->var = cloneNextWordByDelimiter(&(words[thisWord]),'=');
          rootVar->notEqual = (rootVar->var[strlen(rootVar->var)-1] == '!'); // requested not equal
          if (rootVar->notEqual)
              rootVar->var[strlen(rootVar->var)-1] = 0;
          // Do not try to combine repeated vars because "foo=a foo=b" is 'AND' while "foo=a,b" is 'OR'.
  
          // Fill in the val(s) from second half of pair
          char *val = NULL;
          if (words[thisWord][0] != '\0' && words[thisWord][0] != '?') // "var=?" or "var=" will query by var name only
              val = cloneString(words[thisWord]);
          if (val != NULL)
              {
              // Strip any single or double quotes first.
              char *end = val + strlen(val) - 1;
              if ((*val == '"'  && *end == '"')
              ||  (*val == '\'' && *end == '\''))
                  {
                  *end = '\0';
                  val++;
                  }
              // handle comma separated list of vals (if unquoted)
              if (strchr(val,',') != NULL)
                  {
                  char * aVal = NULL;
                  while((aVal = cloneNextWordByDelimiter(&val,',')) != NULL)
                      {
                      AllocVar(limbVal);
                      limbVal->val = aVal;
                      slAddTail(&rootVar->vals,limbVal);
                      }
                  }
              else
                  {
                  AllocVar(limbVal);
                  limbVal->val = val;
                  rootVar->vals = limbVal;
                  }
              }
          hashAdd(varHash, rootVar->var, rootVar);
          slAddHead(&mdbByVars,rootVar);
          }
      freeMem(words);
      slReverse(&mdbByVars);
      verbose(3, "mdbByVarsLineParse() parsed:%d first: %s%s='%s'.\n",
          slCount(mdbByVars),mdbByVars->var,(mdbByVars->notEqual?"!":""),(mdbByVars->vals?mdbByVars->vals->val:""));
  return mdbByVars;
  }
  
  // ------ Loading from args, hashes and tdb ------
  struct mdbByVar*mdbByVarCreate(char *var, char *val)
  /* Creates a singular var=val pair struct for metadata queries. */
  {
  struct mdbByVar *mdbByVar = NULL;
  
      if(var == NULL)
          errAbort("Need variable to create mdbByVar query object.\n");
  
      AllocVar(mdbByVar);
      mdbByVar->var = cloneString(var);
  
      if(val != NULL)
          {
          struct mdbLimbVal * limbVal;
          AllocVar(limbVal);
  
          limbVal->val    = cloneString(val);
          mdbByVar->vals = limbVal; // Only one
          }
  
  return mdbByVar;
  }
  
  boolean mdbByVarAppend(struct mdbByVar *mdbByVars,char *var,char *val,boolean notEqual)
  /* Adds a another var to a list of mdbByVar pairs to be used in metadata queries. */
  {
  // Does var already exist in mdbByVars?
  struct mdbByVar *mdbByVar = mdbByVars;
  for(;mdbByVar!=NULL;mdbByVar=mdbByVar->next)
      {
      if (sameString(mdbByVar->var,var) && mdbByVar->notEqual == notEqual)
          {
          struct mdbLimbVal * limbVal = mdbByVar->vals;
          for(;limbVal!=NULL;limbVal=limbVal->next)
              {
              if (sameString(limbVal->val,val))
                  return FALSE; // Nothing to do as this var is already there.
              }
          struct mdbLimbVal * newLimbVal;
          AllocVar(newLimbVal);
  
          newLimbVal->val    = cloneString(val);
          slAddTail(&(mdbByVar->vals),newLimbVal);
          return TRUE;
          }
      }
  
  // Not found so add it
  struct mdbByVar *newVar = mdbByVarCreate(var, val);
  newVar->notEqual = notEqual;
  slAddTail(&mdbByVars,newVar); // Add to tail to avoid changing passed in pointer
  
  return TRUE;
  }
  
  struct mdbObj *mdbObjCreate(char *obj,char *var, char *val)
  /* Creates a singular mdbObj query object based on obj and all other optional params. */
  {
  struct mdbObj *mdbObj = NULL;
  
      if(obj == NULL)
          errAbort("Need obj to create mdbObj object.\n");
  
      AllocVar(mdbObj);
      mdbObj->obj     = cloneString(obj);
  
      if(var != NULL)
          {
          struct mdbVar * mdbVar;
          AllocVar(mdbVar);
  
          mdbVar->var     = cloneString(var);
          if(val != NULL)
              mdbVar->val     = cloneString(val);
          mdbObj->vars = mdbVar; // Only one
          }
  return mdbObj;
  }
  
  struct mdbObj *mdbObjNew(char *obj,struct mdbVar *mdbVars)
  // Returns a new mdbObj with whatever was passed in.
  // An mdbObj requires and obj, so if one is not supplied it will be "[unknown]"
  {
  struct mdbObj *mdbObj = NULL;
  if (obj == NULL)
      errAbort("Need obj to create mdbObj object.\n");
  
  if (mdbVars == NULL)
      {
      AllocVar(mdbObj);
      mdbObj->obj = cloneString(obj);
      return mdbObj;
      }
  else
      {
      mdbObj = mdbObjCreate(obj,mdbVars->var,mdbVars->val);
      mdbObj->varHash = hashNew(0);
      hashAddUnique(mdbObj->varHash, mdbVars->var, mdbObj->vars); // pointer to struct to resolve type
  
      struct mdbVar *var = mdbVars->next;
      for(;var != NULL;var = var->next);
          mdbObjSetVar(mdbObj, var->var,var->val);
      }
  return mdbObj;
  }
  
  struct mdbObj *mdbObjsLoadFromHashes(struct hash *objsHash)
  // Load all mdbObjs from a file containing metadata formatted lines
  {
  struct mdbObj *mdbObjs = NULL;
  struct hashEl* objEl = NULL;
  
  struct hashCookie objCookie = hashFirst(objsHash);
  while((objEl = hashNext(&objCookie)) != NULL)
      {
      struct mdbObj *mdbObj;
      AllocVar(mdbObj);
      mdbObj->obj     = cloneString(objEl->name);
      mdbObj->varHash = hashNew(0);
      struct hash *hashedVars = objEl->val;
      struct hashCookie varCookie = hashFirst(hashedVars);
      struct hashEl* varEl = NULL;
      while((varEl = hashNext(&varCookie)) != NULL)
          {
          if(sameString(varEl->name,MDB_METAOBJ_RAKEY))
              continue;
  
          struct mdbVar * mdbVar;
          AllocVar(mdbVar);
          mdbVar->var     = cloneString(varEl->name);
          mdbVar->val     = cloneString(varEl->val);
          hashAdd(mdbObj->varHash, mdbVar->var, mdbVar); // pointer to struct to resolve type
          slAddHead(&(mdbObj->vars),mdbVar);
          }
          slSort(&(mdbObj->vars),&mdbVarCmp); // Should be in determined order
          slAddHead(&mdbObjs,mdbObj);
      }
      slSort(&mdbObjs,&mdbObjCmp); // Should be in determined order
      return mdbObjs;
  }
  
  // ------ Loading from files ------
  struct mdbObj *mdbObjsLoadFromFormattedFile(char *fileName,boolean *validated)
  // Load all mdbObjs from a file containing metadata formatted lines
  {
  struct mdbObj *mdbObjs = NULL;
  struct lineFile *lf = lineFileOpen(fileName, TRUE);
  char *line;
  
  while (lineFileNext(lf, &line,NULL))
      {
      char *start = skipLeadingSpaces(line);
      if(start == NULL || *start == '#')
          continue;
      if(startsWithWord(MDB_METAOBJ_RAKEY,line))
          {
          // This is the RA style file!!
          lineFileClose(&lf);
          return mdbObjsLoadFromRAFile(fileName,validated);
          }
      struct mdbObj *mdbObj = metadataLineParse(line);
      if(mdbObj == NULL)
          {
          mdbObjsFree(&mdbObjs);
          return NULL;
          }
      slAddHead(&mdbObjs,mdbObj);
      }
      lineFileClose(&lf);
      slReverse(&mdbObjs);  // Go ahead and keep this in file order
      if(validated)
          *validated = FALSE;
      return mdbObjs;
  }
  
  #define MDB_MAGIC_PREFIX "# MAGIC: "
  struct mdbObj *mdbObjsLoadFromRAFile(char *fileName,boolean *validated)
  // Load all mdbObjs from a file containing RA formatted 'metaObjects'
  {
  struct hash *mdHash = raReadAll(fileName, MDB_METAOBJ_RAKEY);
  if(mdHash == NULL)
      {
      verbose(1,"Missing, empty or badly formated RA file:%s\n",fileName);
      return NULL;
      }
  struct mdbObj *mdbObjs = mdbObjsLoadFromHashes(mdHash);
  hashFree(&mdHash);
  
  // Try to validate file
  if(validated)
      {
      *validated = FALSE;
      struct lineFile *lf = lineFileOpen(fileName, TRUE);
      char *line = lineFileSkipToLineStartingWith(lf,MDB_MAGIC_PREFIX,1000000);
      if(line != NULL)
          {
          int fileMagic = atoi(line+strlen(MDB_MAGIC_PREFIX));
          int objsMagic = mdbObjCRC(mdbObjs);
          verbose(3,"Objects magic: %d  Files magic: %d (%s)\n",objsMagic,fileMagic,line+strlen(MDB_MAGIC_PREFIX));
          *validated = (fileMagic == objsMagic);
          }
      else
          verbose(3,"Can't find magic number on this file.\n");
      }
  return mdbObjs;
  }
  
  // ------ Table name and creation ------
  
  void mdbReCreate(struct sqlConnection *conn,char *tblName,boolean testOnly)
  // Creates ore Recreates the named mdb.
  {
  char *sqlCreate =
  "# Contains metadata for a table, file or other objects.\n"
  "CREATE TABLE %s (\n"
  "    obj varchar(255) not null,      # Object name or ID.\n"
  "    var varchar(255) not null,      # Metadata variable name.\n"
  "    val varchar(2048) not null,     # Metadata value.\n"
  "  #Indices\n"
  "    PRIMARY KEY(obj,var),\n"
  "    INDEX varKey (var,val(32),obj)\n"
  ")";
  
  if(sqlTableExists(conn,tblName))
      verbose(2, "Table '%s' already exists.  It will be recreated.\n",tblName);
  
  struct dyString *dy = newDyString(512);
  dyStringPrintf(dy, sqlCreate, tblName);
  verbose(2, "Requesting table creation:\n%s;\n", dyStringContents(dy));
  if(!testOnly)
      sqlRemakeTable(conn, tblName, dyStringContents(dy));
  
  dyStringFree(&dy);
  }
  
  #define HG_CONF_SANDBOX_MDB "db.metaDb"
  #define HG_CONF_SANDBOX_TDB "db.trackDb"
  #define SANDBOX_TDB_ROOT    "trackDb"
  static char*mdbTableNamePreferSandbox()
  // returns the mdb table name or NULL if conn supplied but the table doesn't exist
  {
  char *table = cfgOption(HG_CONF_SANDBOX_MDB);
  if(table != NULL)
      return cloneString(table);
  
  // Look for trackDb name to model
  char *name = cfgOption(HG_CONF_SANDBOX_TDB);
  if(name == NULL)
      return cloneString(MDB_DEFAULT_NAME);
  
  // Only take the last table of a list of tables!
  char delimit = ',';
  for (table = name; (name = skipBeyondDelimit(name,delimit)) != NULL;)
      table = name;
  name = skipLeadingSpaces(table);
  
  // Divide name into root and sandbox portion
  char *root = NULL;
  char *sand = NULL;
  delimit = '_';
  if ((sand = strchr(name,delimit)) == NULL)
      {
      delimit = '-';
      sand = strchr(name,delimit);
      }
  if (sand == NULL) // No sandbox portion
      return cloneString(MDB_DEFAULT_NAME);
  
  root = cloneNextWordByDelimiter(&name,delimit);
  sand = name;
  
  // Since db.trackDb was used, make sure to swap it
  if (startsWith(SANDBOX_TDB_ROOT,root))
      {
      freeMem(root);
      root = cloneString(MDB_DEFAULT_NAME);
      }
  else // If discovered anything other than trackDb then give up as too obscure
      return cloneString(MDB_DEFAULT_NAME);
  
  // Finally ready to put it together
  int size = strlen(root) + strlen(sand) + 2;
  table = needMem(size);
  safef(table,size,"%s%c%s",root,delimit,sand);
  freeMem(root);
  
  return table;
  }
  
  char*mdbTableName(struct sqlConnection *conn,boolean mySandBox)
  // returns the mdb table name or NULL if conn supplied but the table doesn't exist
  {
  char *table = NULL;
  if (mySandBox)
      table = mdbTableNamePreferSandbox();
  if (table == NULL)
      table = cloneString(MDB_DEFAULT_NAME);
  
  // Test for table
  if (conn != NULL && !sqlTableExists(conn,table))
      {
      if (!mySandBox || sameWord(table,MDB_DEFAULT_NAME)) // Then try the root
          {
          freeMem(table);
          return NULL;
          }
      freeMem(table);
      table = cloneString(MDB_DEFAULT_NAME);
      if (!sqlTableExists(conn,table))
          {
          freeMem(table);
          return NULL;
          }
      }
  
  return table;
  }
  
  // -------------- Updating the DB --------------
  int mdbObjsSetToDb(struct sqlConnection *conn,char *tableName,struct mdbObj *mdbObjs,boolean replace,boolean testOnly)
  // Adds or updates metadata obj/var pairs into the named table.  Returns total rows affected
  {
  char query[8192];
  struct mdbObj *mdbObj;
  struct mdbVar *mdbVar;
  int count = 0;
  
  if(tableName == NULL)
      tableName = mdbTableName(conn,TRUE); // defaults to sandbox, if it exists, else MDB_DEFAULT_NAME;
  else if(!sqlTableExists(conn,tableName))
      errAbort("mdbObjsSetToDb attempting to update non-existent table named '%s'.\n",tableName);
  
  // Table specific lock (over-cautious, since most work is done on sandbox tables)
  char lock[64];
  safef(lock,sizeof lock,"lock_%s",tableName);
  sqlGetLock(conn, lock);
  
  for(mdbObj = mdbObjs;mdbObj != NULL; mdbObj = mdbObj->next)
      {
      // Handle delete requests first
      if(mdbObj->deleteThis)
          {
          if(mdbObj->vars == NULL) // deletes all
              {
              safef(query, sizeof(query),"%s where obj = '%s'",tableName,mdbObj->obj);
              int delCnt = sqlRowCount(conn,query);
  
              if(delCnt>0)
                  {
                  safef(query, sizeof(query),
                      "delete from %s where obj = '%s'",tableName,mdbObj->obj);
                  verbose(2, "Requesting delete of %d rows:\n\t%s;\n",delCnt, query);
                  if(!testOnly)
                      sqlUpdate(conn, query);
                  count += delCnt;
                  }
              }
          else  // deletes selected vars
              {
              for(mdbVar = mdbObj->vars;mdbVar != NULL; mdbVar = mdbVar->next)
                  {
                  safef(query, sizeof(query),
                      "select obj from %s where obj = '%s' and var = '%s'",
                      tableName,mdbObj->obj,mdbVar->var);
                  if(sqlExists(conn,query))
                      {
                      safef(query, sizeof(query),
                          "delete from %s where obj = '%s' and var = '%s'",
                          tableName,mdbObj->obj,mdbVar->var);
                      verbose(2, "Requesting delete of 1 row:\n\t%s;\n",query);
                      if(!testOnly)
                          sqlUpdate(conn, query);
                      count++;
                      }
                  }
              }
          continue;  // Done with this mdbObj
          }
      else if (replace)  // If replace then clear out deadwood before inserting new vars
          {
          safef(query, sizeof(query),"%s where obj = '%s'",tableName,mdbObj->obj);
          int delCnt = sqlRowCount(conn,query);
  
          if(delCnt>0)
              {
              safef(query, sizeof(query),
                  "delete from %s where obj = '%s'",tableName,mdbObj->obj);
              verbose(2, "Requesting replacement of %d rows:\n\t%s;\n",delCnt, query);
              if(!testOnly)
                  sqlUpdate(conn, query);
              count += delCnt;
              }
          }
  
      // Now it is time for update or add!
      for(mdbVar = mdbObj->vars;mdbVar != NULL; mdbVar = mdbVar->next)
          {
          stripEnclosingDoubleQuotes(mdbVar->val); // Ensures values are stripped of enclosing quotes
  
          // Be sure to check for var existence first, then update
          if (!replace)
              {
              struct mdbObj *objExists = mdbObjQueryByObj(conn,tableName,mdbObj->obj,mdbVar->var);
              if(objExists)
                  {
                  if(differentString(mdbVar->val,objExists->vars->val))
                      {
                      safef(query, sizeof(query),
                          "update %s set val = '%s' where obj = '%s' and var = '%s'",
                              tableName,
                              sqlEscapeString(mdbVar->val),
                              mdbObj->obj,mdbVar->var);
                      verbose(2, "Requesting update of 1 row:\n\t%s;\n",query);
                      if(!testOnly)
                          sqlUpdate(conn, query);
                      count++;
                      }
                  mdbObjsFree(&objExists);
                  continue;  // The object was found/updated so done with it
                  }
              }
          // Finally ready to insert new vars
          safef(query, sizeof(query),
              "insert into %s set obj='%s', var='%s', val='%s'",
                  tableName,mdbObj->obj,mdbVar->var,
                  sqlEscapeString(mdbVar->val)); // FIXME Strip quotes
          verbose(2, "Requesting insert of one row:\n\t%s;\n",query);
          if(!testOnly)
              sqlUpdate(conn, query);
          count++;
          }
      }
  sqlReleaseLock(conn, lock);
  return count;
  }
  
  int mdbObjsLoadToDb(struct sqlConnection *conn,char *tableName,struct mdbObj *mdbObjs,boolean testOnly)
  // Adds mdb Objs with minimal error checking
  {
  int count = 0;
 +verboseTime(2, "Start of mdbObjsLoadToDb %s", tableName);
  
  if (tableName == NULL)
      tableName = mdbTableName(conn,TRUE); // defaults to sandbox, if it exists, else MDB_DEFAULT_NAME;
  else if (!sqlTableExists(conn,tableName))
      errAbort("mdbObjsLoadToDb attempting to load non-existent table named '%s'.\n",tableName);
  
  assert(mdbObjs != NULL);  // If this is the case, then be vocal
  
  #define MDB_TEMPORARY_TAB_FILE "temporaryMdb.tab"
  long lastTime = 0;
  
  count = mdbObjPrintToTabFile(mdbObjs,MDB_TEMPORARY_TAB_FILE);
 +verboseTime(2, "past mdbObjPrintToTabFile()", tableName);
  
  // Disable keys in hopes of speeding things up.  No danger since it only disables non-unique keys
  char query[8192];
  safef(query, sizeof(query),"alter table %s disable keys",tableName);
  sqlUpdate(conn, query);
  
  // Quick? load
  sqlLoadTabFile(conn, MDB_TEMPORARY_TAB_FILE, tableName, SQL_TAB_FILE_WARN_ON_ERROR|SQL_TAB_FILE_WARN_ON_WARN);
 +verboseTime(2, "past sqlLoadTabFile()");
  
  // Enabling the keys again
  safef(query, sizeof(query),"alter table %s enable keys",tableName);
  sqlUpdate(conn, query);
 +verboseTime(2, "Past alter table");
  
- unlink(MDB_TEMPORARY_TAB_FILE);
+ //unlink(MDB_TEMPORARY_TAB_FILE);
  
  verbose(0,"%04ldms - Done loading mdb with 'LOAD DATA INFILE' mysql command.\n",(clock1000() - lastTime));
  
  return count;
  }
  
  // ------------------ Querys -------------------
  struct mdbObj *mdbObjQuery(struct sqlConnection *conn,char *table,struct mdbObj *mdbObj)
  // Query the metadata table by obj and optional vars and vals in metaObj struct.  If mdbObj is NULL query all.
  // Returns new mdbObj struct fully populated and sorted in obj,var order.
  {
  //  select obj,var,val where (var= [and val=]) or ([var= and] val=) order by obj,var
      boolean buildHash = TRUE;
  
      if(table == NULL)
          table = mdbTableName(conn,TRUE); // defaults to sandbox, if it exists, else MDB_DEFAULT_NAME;
      else if(!sqlTableExists(conn,table))
          return NULL;
  
      struct dyString *dy = newDyString(4096);
      dyStringPrintf(dy, "select obj,var,val from %s", table);
      if(mdbObj != NULL && mdbObj->obj != NULL)
          {
          dyStringPrintf(dy, " where obj %s '%s'",
              (strchr(mdbObj->obj,'%')?"like":"="),mdbObj->obj);
  
          struct mdbVar *mdbVar;
          for(mdbVar=mdbObj->vars;mdbVar!=NULL;mdbVar=mdbVar->next)
              {
              if(mdbVar==mdbObj->vars)
                  dyStringPrintf(dy, " and (");
              else
                  dyStringPrintf(dy, " or ");
              if(mdbVar->var != NULL)
                  {
                  if(mdbVar->val != NULL)
                      dyStringPrintf(dy, "(");
                  dyStringPrintf(dy, "var %s '%s'",
                      (strchr(mdbVar->var,'%')?"like":"="),mdbVar->var);
                  }
              if(mdbVar->val != NULL)
                  {
                  if(mdbVar->var != NULL)
                      dyStringPrintf(dy, " and ");
                  dyStringPrintf(dy, "val %s '%s'",
                      (strchr(mdbVar->val,'%')?"like":"="), sqlEscapeString(mdbVar->val));
                  if(mdbVar->var != NULL)
                      dyStringPrintf(dy, ")");
                  }
              if(mdbVar->var == NULL && mdbVar->val)
                  errAbort("mdbObjQuery has empty mdbVar struct.\n");
              buildHash = FALSE;  // too few variables
              }
          if(mdbObj->vars != NULL)
              dyStringPrintf(dy, ")");
          }
      dyStringPrintf(dy, " order by binary obj, var");    // binary forces case-sensitive sort
      verbose(2, "Requesting query:\n\t%s;\n",dyStringContents(dy));
  
      struct mdb *mdb = mdbLoadByQuery(conn, dyStringCannibalize(&dy));
      struct mdbObj *mdbObjs = mdbObjsLoadFromMemory(&mdb,buildHash);
      verbose(3, "Returned %d object(s) with %d var(s).\n",
          mdbObjCount(mdbObjs,TRUE),mdbObjCount(mdbObjs,FALSE));
      return mdbObjs;
  }
  
  struct mdbObj *mdbObjQueryByObj(struct sqlConnection *conn,char *table,char *obj,char *var)
  // Query a single metadata object and optional var from a table (default mdb).
  {
  if(obj == NULL)
      return mdbObjQuery(conn,table,NULL);
  
  struct mdbObj *queryObj  = mdbObjCreate(obj,var,NULL);
  struct mdbObj *resultObj = mdbObjQuery(conn,table,queryObj);
  mdbObjsFree(&queryObj);
  return resultObj;
  }
  
  struct mdbByVar *mdbByVarsQuery(struct sqlConnection *conn,char *table,struct mdbByVar *mdbByVars)
  // Query the metadata table by one or more var=val pairs to find the distinct set of objs that satisfy ANY conditions.
  // Returns new mdbByVar struct fully populated and sorted in var,val,obj order.
  {
  //  select obj,var,val where (var= [and val in (val1,val2)]) or (var= [and val in (val1,val2)]) order by var,val,obj
  
      if(table == NULL)
              table = mdbTableName(conn,TRUE); // defaults to sandbox, if it exists, else MDB_DEFAULT_NAME;
      else if(!sqlTableExists(conn,table))
          return NULL;
  
      struct dyString *dy = newDyString(4096);
      dyStringPrintf(dy, "select obj,var,val from %s", table);
  
      struct mdbByVar *rootVar;
      for(rootVar=mdbByVars;rootVar!=NULL;rootVar=rootVar->next)
          {
          if(rootVar==mdbByVars)
              dyStringPrintf(dy, " where (var ");
          else
              dyStringPrintf(dy, " OR (var ");
  
          if(rootVar->notEqual && rootVar->vals == NULL)
              dyStringPrintf(dy, "%s",strchr(rootVar->var,'%')?"NOT ":"!");  // one of: "NOT LIKE". "!=" or "NOT EXISTS"
  
          if(rootVar->vals != NULL && rootVar->vals->val != NULL && strlen(rootVar->vals->val) > 0)
              {
              dyStringPrintf(dy, "%s '%s'",
                  (strchr(rootVar->var,'%')?"like":"="), rootVar->var);
              }
          else
              dyStringPrintf(dy, "EXISTS");
  
          struct mdbLimbVal *limbVal;
          boolean multiVals = FALSE;
          for(limbVal=rootVar->vals;limbVal!=NULL;limbVal=limbVal->next)
              {
              if(limbVal->val == NULL || strlen(limbVal->val) < 1)
                  continue;
  
              if(!multiVals)
                  {
                  dyStringPrintf(dy, " and val ");
                  if(rootVar->notEqual)
                      dyStringPrintf(dy, "%s",strchr(limbVal->val,'%')?"NOT ":"!");
                  if(limbVal->next == NULL) // only one val
                      {
                      dyStringPrintf(dy, "%s '%s'",
                          (strchr(limbVal->val,'%')?"like":"="), sqlEscapeString(limbVal->val));
                      break;
                      }
                  else
                      dyStringPrintf(dy, "in (");
                  multiVals=TRUE;
                  }
              else
                  dyStringPrintf(dy, ",");
              dyStringPrintf(dy, "'%s'", sqlEscapeString(limbVal->val));
              }
          if(multiVals)
              dyStringPrintf(dy, ")");
          dyStringPrintf(dy, ")");
          }
      dyStringPrintf(dy, " order by var, val, obj");
      verbose(2, "Requesting query:\n\t%s;\n",dyStringContents(dy));
  
      struct mdb *mdb = mdbLoadByQuery(conn, dyStringCannibalize(&dy));
      verbose(3, "rows (vars) returned: %d\n",slCount(mdb));
      struct mdbByVar *mdbByVarsFromMem = mdbByVarsLoadFromMemory(&mdb,TRUE);
      verbose(3, "Returned %d vars(s) with %d val(s) with %d object(s).\n",
          mdbByVarCount(mdbByVarsFromMem,TRUE ,FALSE),
          mdbByVarCount(mdbByVarsFromMem,FALSE,TRUE ),
          mdbByVarCount(mdbByVarsFromMem,FALSE,FALSE));
      return mdbByVarsFromMem;
  }
  
  struct mdbByVar *mdbByVarQueryByVar(struct sqlConnection *conn,char *table,char *varName,char *val)
  // Query a single metadata variable and optional val from a table (default mdb) for searching val->obj.
  {
  if(varName == NULL)
      return mdbByVarsQuery(conn,table,NULL);
  
  struct mdbByVar *queryVar  = mdbByVarCreate(varName,val);
  struct mdbByVar *resultVar = mdbByVarsQuery(conn,table,queryVar);
  mdbByVarsFree(&queryVar);
  return resultVar;
  }
  
  struct mdbObj *mdbObjsQueryByVars(struct sqlConnection *conn,char *table,struct mdbByVar *mdbByVars)
  // Query the metadata table by one or more var=val pairs to find the distinct set of objs that satisfy ALL conditions.
  // Returns new mdbObj struct fully populated and sorted in obj,var order.
  {
  // MOST POPULAR WAY TO QUERY MDB.  Building example queries like:
  // "cell=GM12878" or "cell!=GM12878"
  //   SELECT T1.obj,T1.var,T1.val FROM metaDb T1 WHERE EXISTS (SELECT T2.obj FROM metaDb T2 WHERE T2.obj = T1.obj AND T2.var = 'cell' AND T2.val = 'GM12878') ORDER BY T1.obj, T1.var;
  //   SELECT T1.obj,T1.var,T1.val FROM metaDb T1 WHERE EXISTS (SELECT T2.obj FROM metaDb T2 WHERE T2.obj = T1.obj AND T2.var = 'cell' AND T2.val != 'GM12878') ORDER BY T1.obj, T1.var;
  // "cell=GM%" or "cell!=GM%"
  //   SELECT T1.obj,T1.var,T1.val FROM metaDb T1 WHERE EXISTS (SELECT T2.obj FROM metaDb T2 WHERE T2.obj = T1.obj AND T2.var = 'cell' AND T2.val LIKE 'GM%') ORDER BY T1.obj, T1.var;
  //   SELECT T1.obj,T1.var,T1.val FROM metaDb T1 WHERE EXISTS (SELECT T2.obj FROM metaDb T2 WHERE T2.obj = T1.obj AND T2.var = 'cell' AND T2.val NOT LIKE 'GM%') ORDER BY T1.obj, T1.var;
  // "cell=" or "cell!="
  //   SELECT T1.obj,T1.var,T1.val FROM metaDb T1 WHERE EXISTS (SELECT T2.obj FROM metaDb T2 WHERE T2.obj = T1.obj AND T2.var = 'cell') ORDER BY T1.obj, T1.var;
  //   SELECT T1.obj,T1.var,T1.val FROM metaDb T1 WHERE NOT EXISTS (SELECT T2.obj FROM metaDb T2 WHERE T2.obj = T1.obj AND T2.var = 'cell') ORDER BY T1.obj, T1.var;
  // "cell=GM12878,K562" or "cell!=GM12878,K562"
  //   SELECT T1.obj,T1.var,T1.val FROM metaDb T1 WHERE EXISTS (SELECT T2.obj FROM metaDb T2 WHERE T2.obj = T1.obj AND T2.var = 'cell' AND T2.val IN ('GM12878','K562')) ORDER BY T1.obj, T1.var;
  //   SELECT T1.obj,T1.var,T1.val FROM metaDb T1 WHERE EXISTS (SELECT T2.obj FROM metaDb T2 WHERE T2.obj = T1.obj AND T2.var = 'cell' AND T2.val NOT IN ('K562','GM12878')) ORDER BY T1.obj, T1.var;
  // "cell=GM% cell!=GM12878"  (very powerful)
  //   SELECT T1.obj,T1.var,T1.val FROM metaDb T1 WHERE EXISTS (SELECT T2.obj FROM metaDb T2 WHERE T2.obj = T1.obj AND T2.var = 'cell' AND T2.val LIKE 'GM%')
  //                                                           AND EXISTS (SELECT T3.obj FROM metaDb T3 WHERE T3.obj = T1.obj AND T3.var = 'cell' AND T3.val != 'GM12878') ORDER BY T1.obj, T1.var;
  
      if(table == NULL)
          table = mdbTableName(conn,TRUE); // defaults to sandbox, if it exists, else MDB_DEFAULT_NAME;
      else if(!sqlTableExists(conn,table))
          return NULL;
  
      struct dyString *dy = newDyString(4096);
      dyStringPrintf(dy, "SELECT T1.obj,T1.var,T1.val FROM %s T1", table);
  
      struct mdbByVar *rootVar;
      boolean gotVar = FALSE;
      int tix;
      for(rootVar=mdbByVars,tix=2;rootVar!=NULL;rootVar=rootVar->next,tix++)
          {
          boolean hasVal = (rootVar->vals != NULL);
          //boolean hasVal = (rootVar->vals != NULL && rootVar->vals->val != NULL && strlen(rootVar->vals->val) > 0);
          if(!gotVar)
              {
              dyStringPrintf(dy, " WHERE ");
              gotVar=TRUE;
              }
          else
              dyStringPrintf(dy, " AND ");
  
          if(!hasVal && rootVar->notEqual)
              dyStringPrintf(dy, "NOT EXISTS ");
          else
              dyStringPrintf(dy, "EXISTS ");
  
          dyStringPrintf(dy, "(SELECT T%d.obj FROM %s T%d WHERE T%d.obj = T1.obj AND T%d.var ",tix,table,tix,tix,tix);
  
          if(hasVal && rootVar->notEqual && rootVar->vals == NULL)
              dyStringPrintf(dy, "%s",strchr(rootVar->var,'%')?"NOT ":"!");
  
          dyStringPrintf(dy, "%s '%s'",
              (strchr(rootVar->var,'%')?"LIKE":"="), rootVar->var);
  
          struct mdbLimbVal *limbVal;
          boolean multiVals = FALSE;
          for(limbVal=rootVar->vals;limbVal!=NULL;limbVal=limbVal->next)
              {
              if(limbVal->val == NULL || strlen(limbVal->val) < 1)
                  continue;
  
              if(!multiVals)
                  {
                  dyStringPrintf(dy, " AND T%d.val ",tix);
                  if(rootVar->notEqual)
                      dyStringPrintf(dy, "%s",(strchr(limbVal->val,'%') || limbVal->next)?"NOT ":"!");
                  if(limbVal->next == NULL) // only one val
                      {
                      dyStringPrintf(dy, "%s '%s'",
                          (strchr(limbVal->val,'%')?"LIKE":"="), sqlEscapeString(limbVal->val));
                      break;
                      }
                  else
                      dyStringPrintf(dy, "IN (");
                  multiVals=TRUE;
                  }
              else
                  dyStringPrintf(dy, ",");
              dyStringPrintf(dy, "'%s'", sqlEscapeString(limbVal->val));
              }
          if(multiVals)
              dyStringPrintf(dy, ")");
          dyStringPrintf(dy, ")");
          }
      dyStringPrintf(dy, " ORDER BY binary T1.obj, T1.var");  // binary forces case sensitive sort
      verbose(2, "Requesting query:\n\t%s;\n",dyStringContents(dy));
  
      struct mdb *mdb = mdbLoadByQuery(conn, dyStringCannibalize(&dy));
      verbose(3, "rows (vars) returned: %d\n",slCount(mdb));
      struct mdbObj *mdbObjs = mdbObjsLoadFromMemory(&mdb,TRUE);
      verbose(3, "Returned %d object(s) with %d var(s).\n",
          mdbObjCount(mdbObjs,TRUE),mdbObjCount(mdbObjs,FALSE));
      return mdbObjs;
  }
  
  struct mdbObj *mdbObjsQueryByVarValString(struct sqlConnection *conn,char *tableName,char *varVals)
  // returns mdbObjs matching varVals in form of: [var1=val1 var2=val2a,val2b var3=v%3 var4="val 4" var5!=val5 var6=]
  //   var2=val2a,val2b: matches asny of comma separated list
  //   var3=v%3        : matches '%' and '?' wild cards.
  //   var4="val 4"    : matches simple double quoted strings.
  //   var5!=val5      : matches not equal.
  //   var6=           : matches that var exists (same as var6=%). var6!= also works.
  {
  struct mdbByVar *mdbByVars = mdbByVarsLineParse(varVals);
  if (mdbByVars == NULL)
      return NULL;
  return mdbObjsQueryByVars(conn,tableName,mdbByVars);
  }
  
  struct mdbObj *mdbObjsQueryByVarPairs(struct sqlConnection *conn,char *tableName,struct slPair *varValPairs)
  // returns mdbObjs matching varValPairs provided.
  //   The != logic of mdbObjsQueryByVarValString() is not possible, but other cases are supported:
  //   as val may be NULL, a comma delimited list, double quoted string, containing wilds: % and ?
  {
  // Note: there is a bit of inefficiency creating a string then tearing it down, but it streamlines code
  char *varValString = slPairListToString(varValPairs,TRUE); // quotes added when spaces found
  struct mdbObj *mdbObjs = mdbObjsQueryByVarValString(conn,tableName,varValString);
  freeMem(varValString);
  return mdbObjs;
  }
  
  struct mdbObj *mdbObjQueryCompositeObj(struct sqlConnection *conn,char *tableName,struct mdbObj *mdbObj)
  // returns NULL or the composite mdbObj associated with the object passed in.
  {
  char *objType = mdbObjFindValue(mdbObj,MDB_OBJ_TYPE);
  assert(objType != NULL);
  if (sameWord(objType,MDB_VAR_COMPOSITE))
      return mdbObjClone(mdbObj);
  char *compName = mdbObjFindValue(mdbObj,MDB_VAR_COMPOSITE);
  if (compName == NULL)
      return NULL;
  
  return mdbObjQuery(conn,tableName,mdbObjCreate(compName,NULL,NULL));
  }
  
  
  // ----------- Printing and Counting -----------
  static void mdbVarValPrint(struct mdbVar *mdbVar,boolean raStyle, FILE *outF)
  {
  if(mdbVar != NULL && mdbVar->var != NULL)
      {
      if(raStyle)
          fprintf(outF, "\n%s ",mdbVar->var);
      else
          fprintf(outF, " %s=",mdbVar->var);
      if(mdbVar->val != NULL)
          {
          if(!raStyle && strchr(mdbVar->val, ' ') != NULL) // Has blanks
              fprintf(outF, "\"%s\"",mdbVar->val);
          else
              fprintf(outF, "%s",mdbVar->val);
          }
      }
  }
  
  
  void mdbObjPrintToStream(struct mdbObj *mdbObjs,boolean raStyle, FILE *outF )
  // prints objs and var=val pairs as formatted metadata lines or ra style
  {
  // Single line:
  //   metadata iLoveLucy table lucy=ricky ethyl=fred
  // ra style
  //   metadata iLoveLucy table
  //       lucy ricky
  //       ethy fred
  // TODO: Expand for mutilple var types; strip quotes from vals on ra style
  struct mdbObj *mdbObj = NULL;
  for(mdbObj=mdbObjs;mdbObj!=NULL;mdbObj=mdbObj->next)
      {
      if(mdbObj->obj == NULL)
          continue;
  
      fprintf(outF, "%s %s",(raStyle?MDB_METAOBJ_RAKEY:MDB_METADATA_KEY),mdbObj->obj);
      if(mdbObj->deleteThis)
          fprintf(outF, " delete");
  
      struct mdbVar *mdbVar = NULL;
  
      // If hash available, force objType to front
      if(mdbObj->varHash != NULL)
          {
          mdbVar = hashFindVal(mdbObj->varHash,MDB_OBJ_TYPE);
          mdbVarValPrint(mdbVar,raStyle, outF);
          }
      for(mdbVar=mdbObj->vars;mdbVar!=NULL;mdbVar=mdbVar->next)
          {
          if(mdbObj->varHash == NULL || !sameOk(MDB_OBJ_TYPE,mdbVar->var))
              mdbVarValPrint(mdbVar,raStyle, outF);
          }
      fprintf(outF, "%s",(raStyle?"\n\n":"\n"));
      }
  if(raStyle) // NOTE: currently only supporting validation of RA files
      fprintf(outF, "%s%d\n",MDB_MAGIC_PREFIX,mdbObjCRC(mdbObjs));
  }
  
  char *mdbObjVarValPairsAsLine(struct mdbObj *mdbObj,boolean objTypeExclude)
  // returns NULL or a line for a single mdbObj as "var1=val1; var2=val2 ...".  Must be freed.
  {
  if (mdbObj!=NULL)
      {
      struct dyString *dyLine = dyStringNew(128);
      struct mdbVar *mdbVar = NULL;
  
      // If hash available, force objType to front
      if (!objTypeExclude && mdbObj->varHash != NULL)
          {
          mdbVar = hashFindVal(mdbObj->varHash,MDB_OBJ_TYPE);
          dyStringPrintf(dyLine,"%s=%s; ",mdbVar->var,mdbVar->val);
          }
      for(mdbVar=mdbObj->vars;mdbVar!=NULL;mdbVar=mdbVar->next)
          {
          if (!sameOk(MDB_OBJ_TYPE,mdbVar->var) || (!objTypeExclude && mdbObj->varHash == NULL))
              dyStringPrintf(dyLine,"%s=%s; ",mdbVar->var,mdbVar->val);
          }
      char *line = dyStringCannibalize(&dyLine);
      if (line)
          {
          int len = strlen(line);
          if (len == 0)
              {
              freeMem(line);
              return NULL;
              }
          if (line[len-1] == ' ')
              line[len-1] = '\0';
          return line;
          }
      }
  return NULL;
  }
  
  void mdbObjPrint(struct mdbObj *mdbObjs,boolean raStyle)
  // prints objs and var=val pairs as formatted metadata lines or ra style
  {
  mdbObjPrintToStream(mdbObjs, raStyle, stdout);
  }
  
  void mdbObjPrintToFile(struct mdbObj *mdbObjs,boolean raStyle, char *file)
  // prints (to file) objs and var=val pairs as formatted metadata lines or ra style
  {
  FILE *f = mustOpen(file, "w");
  
  mdbObjPrintToStream(mdbObjs, raStyle, f);
  
  fclose(f);
  }
  
  int mdbObjPrintToTabFile(struct mdbObj *mdbObjs, char *file)
  // prints all objs as tab delimited obj var val into file for SQL LOAD DATA.  Returns count.
  {
  FILE *tabFile = mustOpen(file, "w");
  int count = 0;
  
  struct mdbObj *mdbObj = NULL;
  for(mdbObj=mdbObjs;mdbObj!=NULL;mdbObj=mdbObj->next)
      {
      if(mdbObj->obj == NULL)
          continue;
  
      struct mdbVar *mdbVar = NULL;
      for(mdbVar=mdbObj->vars;mdbVar!=NULL;mdbVar=mdbVar->next)
          {
          if (mdbVar->var == NULL || mdbVar->val == NULL)
              continue;
          fprintf(tabFile, "%s\t%s\t%s\n",mdbObj->obj,mdbVar->var,sqlEscapeString(mdbVar->val));
          count++;
          }
      }
  
  fclose(tabFile);
  return count;
  }
  
  void mdbByVarPrint(struct mdbByVar *mdbByVars,boolean raStyle)
  // prints var=val pairs and objs that go with them single lines or ra style
  {
  // Single line:
  //   mdbVariable lucy=ethyl bestFriends lifePartners
  //   mdbVariable lucy=ricky iLoveLucy divorces
  // NOT QUITE ra style
  //   metadata Fred wife=Ethyl
  //   metadata Lucy wife=Ethyl
  // Results in:
  //   mdbVariable wife Ethyl
  //   metaObject Fred
  //   metaObject Lucy
  struct mdbByVar *rootVar = NULL;
  for(rootVar=mdbByVars;rootVar!=NULL;rootVar=rootVar->next)
      {
      if(rootVar->var == NULL)
          continue;
  
      struct mdbLimbVal *limbVal = NULL;
      for(limbVal=rootVar->vals;limbVal!=NULL;limbVal=limbVal->next)
          {
          if(limbVal->val == NULL)
              continue;
  
          if(raStyle)
              printf("%s %s ",MDB_METAVAR_RAKEY,rootVar->var);
          else
              printf("%s %s=",MDB_METAVAR_RAKEY,rootVar->var);
  
          if(!raStyle && strchr(limbVal->val, ' ') != NULL) // Has blanks
              printf("\"%s\"",limbVal->val);
          else
              printf("%s",limbVal->val);
  
          struct mdbLeafObj *leafObj = NULL;
          for(leafObj=limbVal->objs;leafObj!=NULL;leafObj=leafObj->next)
              {
              if(leafObj->obj == NULL)
                  continue;
  
              if(raStyle)
                  printf("\n%s %s",MDB_METAOBJ_RAKEY,leafObj->obj);
              else
                  printf(" %s",leafObj->obj);
              }
          printf("\n");
          if(raStyle)
              printf("\n");
          }
      }
  }
  
  
  int mdbObjCount(struct mdbObj *mdbObjs,boolean objs)
  // returns the count of vars belonging to this obj or objs;
  {
  int count = 0;
  struct mdbObj *mdbObj = NULL;
  for(mdbObj=mdbObjs;mdbObj!=NULL;mdbObj=mdbObj->next)
      {
      if(mdbObj->obj == NULL)
          continue;
      if(objs)
          count++;
      else
          {
          struct mdbVar *mdbVar = NULL;
          for(mdbVar=mdbObj->vars;mdbVar!=NULL;mdbVar=mdbVar->next)
              {
              if(mdbVar->var != NULL && mdbVar->val != NULL)
                  count++;
              }
          }
      }
  
  return count;
  }
  
  int mdbByVarCount(struct mdbByVar *mdbByVars,boolean vars, boolean vals)
  // returns the count of objs belonging to this set of vars;
  {
  int count = 0;
  struct mdbByVar *rootVar = NULL;
  for(rootVar=mdbByVars;rootVar!=NULL;rootVar=rootVar->next)
      {
      if(rootVar->var == NULL)
          continue;
      if(vars)
          count++;
      else
          {
          struct mdbLimbVal *limbVal = NULL;
          for(limbVal=rootVar->vals;limbVal!=NULL;limbVal=limbVal->next)
              {
              if(limbVal->val == NULL)
                  continue;
              if(vals)
                  count++;
              else
                  {
                  struct mdbLeafObj *leafObj = NULL;
                  for(leafObj=limbVal->objs;leafObj!=NULL;leafObj=leafObj->next)
                      {
                      if(leafObj->obj != NULL)
                          count++;
                      }
                  }
              }
          }
      }
  return count;
  }
  
  // ----------------- Utilities -----------------
  
  struct mdbVar *mdbObjFind(struct mdbObj *mdbObj, char *var)
  // Finds the mdbVar associated with the var or returns NULL
  {
  if (mdbObj == NULL)
      return NULL;
  
  struct mdbVar *mdbVar = NULL;
  if(mdbObj->varHash != NULL)
      mdbVar = hashFindVal(mdbObj->varHash,var); // case sensitive (unfortunately)
  else
      {
      for(mdbVar=mdbObj->vars;mdbVar!=NULL;mdbVar=mdbVar->next)
          {
          if(sameWord(var,mdbVar->var)) // case insensitive
              break;
          }
      }
  if(mdbVar == NULL)
      return NULL;
  
  return mdbVar;
  }
  
  char *mdbObjFindValue(struct mdbObj *mdbObj, char *var)
  // Finds the val associated with the var or retruns NULL
  {
  struct mdbVar *mdbVar = mdbObjFind(mdbObj, var);
  
  if(mdbVar == NULL)
      return NULL;
  
  return mdbVar->val;
  }
  
  boolean mdbObjContains(struct mdbObj *mdbObj, char *var, char *val)
  // Returns TRUE if object contains var, val or both
  {
  if (mdbObj == NULL)
      return FALSE;
  
  if(var != NULL)
      {
      char *foundVal = mdbObjFindValue(mdbObj,var);
      if(foundVal == NULL)
          return FALSE;
      if(val == NULL)
          return TRUE;
      return sameOk(foundVal,val);
      }
  struct mdbVar *mdbVar = NULL;
  for(mdbVar=mdbObj->vars;mdbVar!=NULL;mdbVar=mdbVar->next)
      {
      if(differentStringNullOk(var,mdbVar->var) != 0)
          continue;
      if(differentStringNullOk(val,mdbVar->val) != 0)
          continue;
      return TRUE;
      }
  
  return FALSE;
  }
  
  boolean mdbObjsContainAltleastOneMatchingVar(struct mdbObj *mdbObjs, char *var, char *val)
  // Returns TRUE if any object in set contains var
  {
  struct mdbObj *mdbObj = mdbObjs;
  for(;mdbObj!=NULL; mdbObj=mdbObj->next)
      {
      if(mdbObjContains(mdbObj, var, val))
          return TRUE;
      }
  return FALSE;
  }
  
  #define MDB_COMMON_VARS_OBJ_SEARCH_LIMIT 10
  struct mdbObj *mdbObjsCommonVars(struct mdbObj *mdbObjs)
  // Returns a new mdbObj with all vars that are contained in every obj passed in.
  // Note that the returnd mdbObj has a meaningles obj name and vals.
  {
  if (mdbObjs == NULL || mdbObjs->vars == NULL)
      return NULL;
  struct mdbObj *mdbObj = mdbObjs;
  struct mdbObj *commonVars = mdbObjClone(mdbObj); // Clone the first obj then prune it
  commonVars->next = NULL;
  mdbObj=mdbObj->next;                             // No need to include first obj in search
  if (mdbObj != NULL)
      {
      int count = 1;
      // NOTE: This should not loop through all, as the list could be huge.  Just compare the first 10 for now
      struct dyString *dyPruneVars = dyStringNew(512);
      for(;mdbObj != NULL && count < MDB_COMMON_VARS_OBJ_SEARCH_LIMIT;mdbObj=mdbObj->next, count++)
          {
          struct mdbVar *mdbVar = commonVars->vars;            // Will walk through the first obj's vars
          for(; mdbVar != NULL; mdbVar = mdbVar->next )
              {
              if (mdbObjsContainAtleastOne(mdbObj, mdbVar->var) == FALSE)
                  dyStringPrintf(dyPruneVars,"%s ",mdbVar->var);  // var not found so add to prune list
              }
          if (dyStringLen(dyPruneVars) > 0)
              {
              mdbObjRemoveVars(commonVars,dyStringContents(dyPruneVars));
              dyStringClear(dyPruneVars);
              }
          }
      dyStringFree(&dyPruneVars);
      }
  return commonVars;
  }
  
  boolean mdbByVarContains(struct mdbByVar *mdbByVar, char *val, char *obj)
  // Returns TRUE if var contains val, obj or both
  {
  if (mdbByVar != NULL)
      {
      struct mdbLimbVal *limbVal = NULL;
      struct mdbLeafObj *leafObj = NULL;
      if(mdbByVar->valHash != NULL && val != NULL)
          {
          limbVal = hashFindVal(mdbByVar->valHash,val);
          if(limbVal == NULL || limbVal->val == NULL)
              return FALSE;
          if(limbVal->objHash != NULL && obj != NULL)
              {
              leafObj = hashFindVal(limbVal->objHash,obj);
              if(leafObj == NULL)
                  return FALSE;
              return sameOk(leafObj->obj,obj);
              }
          }
      for(limbVal=mdbByVar->vals;limbVal!=NULL;limbVal=limbVal->next)
          {
          if(differentStringNullOk(val,limbVal->val) != 0)
              continue;
  
          for(leafObj=limbVal->objs;leafObj!=NULL;leafObj=leafObj->next)
              {
              if(differentStringNullOk(obj,leafObj->obj) != 0)
                  continue;
              return TRUE;
              }
          }
      }
  return FALSE;
  }
  
  void mdbObjReorderVars(struct mdbObj *mdbObjs, char *vars,boolean back)
  // Reorders vars list based upon list of vars "cell antibody treatment".  Send to front or back.
  {
  char *cloneLine = cloneString(vars);
  char **words = NULL;
  if (strchr(cloneLine, ' ') == NULL) // Tolerate alternate delimiters
      {
      if (strchr(cloneLine, ',') != NULL)     // delimit by commas?
          strSwapChar(cloneLine,',',' ');
      else if (strchr(cloneLine, ';') != NULL) // delimit by semicolons?
          strSwapChar(cloneLine,';',' ');
      else if (strchr(cloneLine, '\t') != NULL) // delimit by tabs?
          strSwapChar(cloneLine,'\t',' ');
      }
  int count = chopByWhite(cloneLine,NULL,0);
  if(count)
      {
      words = needMem(sizeof(char *) * count);
      count = chopByWhite(cloneLine,words,count);
      }
  else
      errAbort("mdbObjReorderVars cannot parse vars argument.\n");
  
  struct mdbObj *mdbObj = NULL;
  for( mdbObj=mdbObjs; mdbObj!=NULL; mdbObj=mdbObj->next )
      {
      int ix;
      struct mdbVar *orderedVars = NULL;
      struct mdbVar **varsToReorder = needMem(sizeof(struct mdbVar *) * count);
  
      struct mdbVar *mdbVar = NULL;
      while((mdbVar = slPopHead(&(mdbObj->vars))) != NULL)
          {
          ix = stringArrayIx(mdbVar->var,words,count); // Is case insensitive
          if(ix < 0)
              slAddHead(&orderedVars,mdbVar);
          else
              varsToReorder[ix] = mdbVar;
          }
  
      if(back) // add to front of backward list
          {
          for( ix=0; ix<count; ix++ )
              {
              if(varsToReorder[ix] != NULL) // NOTE: For NULL, could add "None" but that would be too much "inside ball"
                  slAddHead(&orderedVars,varsToReorder[ix]);
              }
          }
      slReverse(&orderedVars);
  
      if(!back)  // Add to front of forward list
          {
          for( ix=count-1; ix>=0; ix-- )
              {
              if(varsToReorder[ix] != NULL)
                  slAddHead(&orderedVars,varsToReorder[ix]);
              }
          }
  
      mdbObj->vars = orderedVars;
      freeMem(varsToReorder);
      }
      freeMem(words);
  }
  
  void mdbObjReorderByCv(struct mdbObj *mdbObjs, boolean includeHidden)
  // Reorders vars list based upon cv.ra typeOfTerms priority
  {
  struct hash *cvTermTypes = (struct hash *)cvTermTypeHash();
  struct hashEl *el, *elList = hashElListHash(cvTermTypes);
  
  struct slPair *cvVars = NULL;
  for (el = elList; el != NULL; el = el->next)
      {
      struct hash *varHash = el->val;
      if (includeHidden || !cvTermIsHidden(el->name)) // Skip the hidden ones
          {
          char *priority = hashFindVal(varHash, CV_TOT_PRIORITY);
          if (priority != NULL) // If there is no priority it will randomly fall to the back of the list
              slPairAdd(&cvVars,el->name,(char *)sqlUnsignedLong(priority));
          }
      }
  hashElFreeList(&elList);
  
  if (cvVars)
      {
      slPairIntSort(&cvVars);  // sorts on the integer val
  
      // Now convert this to a string of names
      char *orderedVars = slPairNameToString(cvVars,' ',FALSE);
      slPairFreeList(&cvVars);
      if (orderedVars != NULL)
          {
          mdbObjReorderVars(mdbObjs, orderedVars,FALSE); // Finally we can reorder the vars in the mdbObjs
          freeMem(orderedVars);
          }
      }
  }
  
  int mdbObjVarCmp(const void *va, const void *vb)
  /* Compare to sort on full list of vars and vals. */
  {
  const struct mdbObj *a = *((struct mdbObj **)va);
  const struct mdbObj *b = *((struct mdbObj **)vb);
  struct mdbVar* aVar = a->vars;
  struct mdbVar* bVar = b->vars;
  for(;aVar != NULL && bVar != NULL;aVar=aVar->next,bVar=bVar->next)
      {
      int ret = differentWord(aVar->var, bVar->var); // case insensitive
      if(ret != 0)
          {
          // Look for it by walking vars
          struct mdbVar* tryVar = bVar->next;
          for(;tryVar;tryVar=tryVar->next)
              {
              if (sameWord(aVar->var, tryVar->var))
                  return -1; // Current aVar found in B so B has extra var and A has NULL: A sorts first
              }
          tryVar = aVar->next;
          for(;tryVar;tryVar=tryVar->next)
              {
              if (sameWord(tryVar->var, bVar->var))
                  return 1; // Current bVar found in A so A has extra var and B has NULL: B sorts first
              }
          return ret;   // Current aVar and bVar are not shared so prioritize them alphabetically (What else can I do?)
          }
      ret = differentString(aVar->val, bVar->val); // case sensitive on val
      if(ret != 0)
          return ret;
      }
  if(bVar != NULL)
      return -1;   // B has extra var and A has NULL: A sorts first
  if(aVar != NULL)
      return 1;    // A has extra var and B has NULL: B sorts first
  return 0;
  }
  
  
  
  void mdbObjsSortOnVars(struct mdbObj **mdbObjs, char *vars)
  // Sorts on var,val pairs vars lists: fwd case-sensitive.  Assumes all objs' vars are in identical order.
  // Optionally give list of vars "cell antibody treatment" to sort on (bringing to front of vars lists).
  {  // NOTE: assumes all var pairs match (e.g. every obj has cell,treatment,antibody,... and missing treatment messes up sort)
  if(vars != NULL)
      mdbObjReorderVars(*mdbObjs,vars,FALSE);
  
  slSort(mdbObjs, mdbObjVarCmp);
  }
  
  void mdbObjsSortOnVarPairs(struct mdbObj **mdbObjs,struct slPair *varValPairs)
  // Sorts on var,val pairs vars lists: fwd case-sensitive.  Assumes all objs' vars are in identical order.
  // This method will use mdbObjsSortOnVars()
  {
  if (varValPairs == NULL)
      return;
  
  struct slPair *onePair = varValPairs;
  struct dyString *dyTerms = dyStringNew(256);
  dyStringAppend(dyTerms,onePair->name);
  onePair = onePair->next;
  for(; onePair != NULL; onePair = onePair->next)
      dyStringPrintf(dyTerms,",%s",onePair->name);
  mdbObjsSortOnVars(mdbObjs,dyStringContents(dyTerms));
  dyStringFree(&dyTerms);
  }
  
  void mdbObjsSortOnCv(struct mdbObj **mdbObjs, boolean includeHidden)
  // Puts obj->vars in order based upon cv.ra typeOfTerms priority,
  //  then case-sensitively sorts all objs in list based upon that var order.
  {  // NOTE: assumes all var pairs match (e.g. every obj has cell,treatment,antibody,... and missing treatment messes up sort)
  mdbObjReorderByCv(*mdbObjs, includeHidden);
  slSort(mdbObjs, mdbObjVarCmp);  // While sort will not be perfect (given missing values) it does a good job none the less.
  }
  
  void mdbObjRemoveVars(struct mdbObj *mdbObjs, char *vars)
  // Prunes list of vars for an object, freeing the memory.  Doesn't touch DB.
  {
  char *cloneLine = NULL;
  int count = 0;
  char **words = NULL;
  if(vars != NULL)
      {
      cloneLine = cloneString(vars);
      count = chopByWhite(cloneLine,NULL,0);
      words = needMem(sizeof(char *) * count);
      count = chopByWhite(cloneLine,words,count);
      }
  struct mdbObj *mdbObj = NULL;
  for( mdbObj=mdbObjs; mdbObj!=NULL; mdbObj=mdbObj->next )
      {
      int ix;
      struct mdbVar *keepTheseVars = NULL;
  
      if(count == 0 && mdbObj->varHash != NULL)
          hashFree(&mdbObj->varHash);
  
      struct mdbVar *mdbVar = NULL;
      while((mdbVar = slPopHead(&(mdbObj->vars))) != NULL)
          {
          if(count == 0)
              ix = 1;
          else
              ix = stringArrayIx(mdbVar->var,words,count);
          if(ix < 0)
              slAddHead(&keepTheseVars,mdbVar);
          else
              {
              if(count != 0 && mdbObj->varHash != NULL)
                  hashRemove(mdbObj->varHash, mdbVar->var);
  
              mdbVarFree(&mdbVar);
              }
          }
  
      if(keepTheseVars != NULL)
          slReverse(&keepTheseVars);
      mdbObj->vars = keepTheseVars;
      }
      if(words != NULL)
          freeMem(words);
  }
  
  void mdbObjRemoveHiddenVars(struct mdbObj *mdbObjs)
  // Prunes list of vars for mdb objs that have been declared as hidden in cv.ra typeOfTerms
  {
  // make comma delimited list of hidden vars
  struct hash *cvTermTypes = (struct hash *)cvTermTypeHash();
  struct hashEl *el, *elList = hashElListHash(cvTermTypes);
  struct dyString *dyRemoveVars = dyStringNew(256);
  
  for (el = elList; el != NULL; el = el->next)
      {
      if (cvTermIsHidden(el->name))
          dyStringPrintf(dyRemoveVars,"%s ",el->name);
      }
  hashElFreeList(&elList);
  
  if (dyStringLen(dyRemoveVars))
      mdbObjRemoveVars(mdbObjs, dyStringContents(dyRemoveVars));
  
  dyStringFree(&dyRemoveVars);
  }
  
  boolean mdbObjsHasCommonVar(struct mdbObj *mdbList, char *var, boolean missingOk)
  // Returns TRUE if all mbObjs passed in have the var with the same value
  {
  char *val = NULL;
  struct mdbObj *mdb = NULL;
  for(mdb = mdbList; mdb; mdb=mdb->next)
      {
      char *thisVal = mdbObjFindValue(mdb,var);
      if (thisVal == NULL)
          {
          if (missingOk)
              continue;
          else
              return FALSE;
          }
      if (val == NULL)
          val = thisVal;
      else if(differentWord(val,thisVal))
          return FALSE;
      }
  return TRUE;
  }
  
  char *mdbRemoveCommonVar(struct mdbObj *mdbList, char *var)
  // Removes var from set of mdbObjs but only if all that have it have a commmon val
  // Returns the val if removed, else NULL
  {
  if (mdbObjsHasCommonVar(mdbList,var,TRUE))  // If var isn't found in some, that is okay
      {
      char *val = NULL;
      struct mdbObj *mdb = mdbList;
      for( ; mdb; mdb=mdb->next)
          {
          if (val == NULL)
              {
              char *thisVal = mdbObjFindValue(mdb,var);
              if (thisVal != NULL)
                  val = cloneString(thisVal);
              }
          mdbObjRemoveVars(mdb,var);
          }
      return val;
      }
  return NULL;
  }
  
  boolean mdbObjSetVar(struct mdbObj *mdbObj, char *var,char *val)
  // Sets the string value to a single var into an obj, preparing for DB update.
  // returns TRUE if updated, FALSE if added
  {
  assert(mdbObj != NULL && var != NULL && val != NULL);
  struct mdbVar *mdbVar = mdbObjFind(mdbObj, var);
  if (mdbVar != NULL)
      {
      if (mdbVar->val != NULL)
          freeMem(mdbVar->val);
      mdbVar->val = cloneString(val);
      if (mdbObj->varHash != NULL)
          hashReplace(mdbObj->varHash, mdbVar->var, mdbVar); // pointer to struct to resolve type
      return TRUE;
      }
  else
      {
      AllocVar(mdbVar);
  
      mdbVar->var     = cloneString(var);
      mdbVar->val     = cloneString(val);
      slAddHead(&mdbObj->vars,mdbVar); // Only one
      if (mdbObj->varHash != NULL)
          hashAdd(mdbObj->varHash, mdbVar->var, mdbVar); // pointer to struct to resolve type
      return FALSE;
      }
  }
  
  boolean mdbObjSetVarInt(struct mdbObj *mdbObj, char *var,int val)
  // Sets an integer value to a single var in an obj, preparing for DB update.
  // returns TRUE if updated, FALSE if added
  {
  char buf[128];
  safef(buf,sizeof(buf),"%d",val);
  return mdbObjSetVar(mdbObj,var,buf);
  }
  
  void mdbObjSwapVars(struct mdbObj *mdbObjs, char *vars,boolean deleteThis)
  // Replaces objs' vars with var=vap pairs provided, preparing for DB update.
  {
  struct mdbObj *mdbObj = NULL;
  for( mdbObj=mdbObjs; mdbObj!=NULL; mdbObj=mdbObj->next )
      {
      mdbObj->deleteThis = deleteThis;
  
      if(mdbObj->varHash != NULL)
          hashFree(&mdbObj->varHash);
  
      mdbVarsFree(&(mdbObj->vars));
  
      mdbObjAddVarPairs(mdbObj,vars);
      }
  }
  
  struct mdbObj *mdbObjsFilter(struct mdbObj **pMdbObjs, char *var, char *val,boolean returnMatches)
  // Filters mdb objects to only those that include/exclude vars.  Optionally checks (case insensitive) val too.
  // Returns matched or unmatched items objects as requested, maintaining sort order
  {
  struct mdbObj *mdbObjsReturned = NULL;
  struct mdbObj *mdbObjs = *pMdbObjs;
  *pMdbObjs = NULL;
  struct mdbObj **pMatchTail   = returnMatches ? &mdbObjsReturned : pMdbObjs;  // Slightly faster than slAddHead/slReverse
  struct mdbObj **pNoMatchTail = returnMatches ? pMdbObjs : &mdbObjsReturned;  // Also known as too clever by half
  while (mdbObjs!=NULL)
      {
      boolean match = FALSE;
      struct mdbObj *obj = slPopHead(&mdbObjs);
      char *foundVal = mdbObjFindValue(obj,var);  // Case sensitive (unfortunately)
      if (val == NULL)
          match = (foundVal != NULL);           // any val will match
      else if (foundVal)
          match = (sameWord(foundVal,val));   // must be same val (case insensitive)
      if (match)
          {
          *pMatchTail = obj;
          pMatchTail = &((*pMatchTail)->next);
          }
      else
          {
          *pNoMatchTail = obj;
          pNoMatchTail = &((*pNoMatchTail)->next);
          }
      }
  return mdbObjsReturned;
  }
  
  struct mdbObj *mdbObjsFilterByVars(struct mdbObj **pMdbObjs,char *vars,boolean noneEqualsNotFound,boolean returnMatches)
  // Filters mdb objects to only those that include/exclude var=val pairs (e.g. "var1=val1 var2 var3!=val3 var4=None").
  // Supports != ("var!=" means var not found). Optionally supports var=None equal to var is not found
  // Returns matched or unmatched items objects as requested.  Multiple passes means sort order is destroyed.
  {
  struct mdbObj *mdbObjsMatch = *pMdbObjs;
  struct mdbObj *mdbObjsNoMatch = NULL;
  char *varsLine = cloneString(vars);
  int ix=0,count = chopByWhite(varsLine,NULL,0);
  char **var = needMem(count * sizeof(char *));
  chopByWhite(varsLine,var,count);
  for(ix=0;ix<count;ix++)
      {
      boolean notEqual = FALSE;
      char *val = strchr(var[ix],'='); // list may be vars alone! (var1=val1 var2 var3!=val3 var4=None)
      if (val != NULL)
          {
          notEqual = (*(val - 1) == '!');
          if (notEqual)
              *(val - 1) = '\0';
          *val = '\0';
          val += 1;
          if (*val == '\0')
              val = NULL;
          }
      struct mdbObj *objNotMatching = mdbObjsFilter(&mdbObjsMatch,var[ix],val,notEqual); // exclude non-matching
      if (noneEqualsNotFound && val != NULL && sameWord(val,MDB_VAL_ENCODE_EDV_NONE))
          mdbObjsMatch = slCat(mdbObjsMatch,mdbObjsFilter(&objNotMatching,var[ix],NULL,notEqual)); // 1st match on var=None, now match on var!= (var not defined)
      mdbObjsNoMatch = slCat(mdbObjsNoMatch,objNotMatching);  // Multiple passes "cat" non-matching and destroys sort order
      }
  freeMem(var);
  freeMem(varsLine);
  if (returnMatches)
      {
      *pMdbObjs = mdbObjsNoMatch;
      return  mdbObjsMatch;
      }
  *pMdbObjs = mdbObjsMatch;
  return  mdbObjsNoMatch;
  }
  
  struct mdbObj *mdbObjsFilterTablesOrFiles(struct mdbObj **pMdbObjs,boolean tables, boolean files)
  // Filters mdb objects to only those that have associated tables or files. Returns removed non-table/file objects
  // Note: Since table/file objects overlap, there are 3 possibilites: tables, files, table && files
  {
  assert(tables || files); // Cant exclude both
  
  struct mdbObj *mdbObjs = *pMdbObjs;
  struct mdbObj *mdbObjsDropped  = NULL;
  if (tables)
      mdbObjsDropped = mdbObjsFilter(&mdbObjs,MDB_OBJ_TYPE,MDB_OBJ_TYPE_TABLE,FALSE);
  
  if (files)
      {
      struct mdbObj *mdbObjsNoFileName = mdbObjsDropped = mdbObjsFilter(&mdbObjs,MDB_VAR_FILENAME,NULL,FALSE);
      if (mdbObjsNoFileName)
          {
          struct mdbObj *mdbObjsNoFileIndex = mdbObjsFilter(&mdbObjsNoFileName,MDB_VAR_FILEINDEX,NULL,FALSE);
          if (mdbObjsNoFileIndex)
              {
              mdbObjs        = slCat(mdbObjs,mdbObjsNoFileName);
              mdbObjsDropped = slCat(mdbObjsDropped,mdbObjsNoFileIndex);
              }
          }
      }
  slSort(&mdbObjs,       &mdbObjCmp); // Need to be returned to obj order
  slSort(&mdbObjsDropped,&mdbObjCmp);
  *pMdbObjs = mdbObjs;
  
  return mdbObjsDropped;
  }
  
  struct mdbObj *mdbObjIntersection(struct mdbObj **pA, struct mdbObj *b)
  // return duplicate objs from an intersection of two mdbObj lists.
  // List b is untouched but pA will contain the resulting intersection
  {
  struct mdbObj *mdbObj;
  struct hash *hashB = newHash(0);
  for (mdbObj = b; mdbObj != NULL; mdbObj = mdbObj->next)
      {
      hashAdd(hashB, mdbObj->obj, mdbObj);
      }
  
  struct mdbObj *mdbObjsDropped = NULL;
  struct mdbObj *mdbObjsIntersecting = NULL;
  struct mdbObj *mdbObjs=*pA;
  while (mdbObjs)
      {
      mdbObj = slPopHead(&mdbObjs);
      if (hashLookup(hashB, mdbObj->obj) != NULL)
          slAddHead(&mdbObjsIntersecting,mdbObj);
      else
          slAddHead(&mdbObjsDropped,mdbObj);
      }
  hashFree(&hashB);
  if (mdbObjsIntersecting)
      slReverse(&mdbObjsIntersecting);
  *pA = mdbObjsIntersecting;
  if (mdbObjsDropped)
      slReverse(&mdbObjsDropped);
  
  return mdbObjsDropped;
  }
  
  void mdbObjTransformToUpdate(struct mdbObj *mdbObjs, char *var, char *val,boolean deleteThis)
  // Turns one or more mdbObjs into the stucture needed to add/update or delete.
  {
  struct mdbObj *mdbObj = NULL;
  for( mdbObj=mdbObjs; mdbObj!=NULL; mdbObj=mdbObj->next )
      {
      mdbObj->deleteThis = deleteThis;
  
      if(mdbObj->varHash != NULL)
          hashFree(&mdbObj->varHash);
  
      mdbVarsFree(&(mdbObj->vars));
  
      if(var != NULL)
          {
          struct mdbVar *mdbVar;
          AllocVar(mdbVar);
  
          mdbVar->var     = cloneString(var);
          if(val != NULL)
              mdbVar->val     = cloneString(val);
          mdbObj->vars = mdbVar; // Only one
          }
      }
  }
  
  struct mdbObj *mdbObjClone(const struct mdbObj *mdbObj)
  // Clones a single mdbObj, including hash and maintining order
  {
  if(mdbObj == NULL)
      return NULL;
  
  struct mdbObj *newObj;
  AllocVar(newObj);
  
  if(mdbObj->obj != NULL)
      newObj->obj    = cloneString(mdbObj->obj);
  newObj->deleteThis = mdbObj->deleteThis;
  if(mdbObj->vars != NULL)
      {
      if(mdbObj->varHash != NULL)
          newObj->varHash = hashNew(0);
  
      struct mdbVar *mdbVar = NULL;
      for(mdbVar = mdbObj->vars; mdbVar != NULL; mdbVar = mdbVar->next )
          {
          struct mdbVar *newVar = NULL;
          AllocVar(newVar);
          if(mdbVar->var != NULL)
              newVar->var = cloneString(mdbVar->var);
          if(mdbVar->val != NULL)
              newVar->val = cloneString(mdbVar->val);
          if(newVar->var != NULL && newVar->val != NULL)
              hashAdd(newObj->varHash, newVar->var, newVar); // pointer to struct to resolve type
          slAddHead(&(newObj->vars),newVar);
          }
      slReverse(&(newObj->vars));
      }
  return newObj;
  }
  
  struct slName *mdbObjToSlName(struct mdbObj *mdbObjs)
  // Creates slNames list of mdbObjs->obj.  mdbObjs remains untouched
  {
  struct slName *mdbNames = NULL;
  struct mdbObj *mdbObj = mdbObjs;
  for( ;mdbObj!=NULL; mdbObj=mdbObj->next)
      {
      slAddHead(&mdbNames,slNameNew(mdbObj->obj)); //allocates memory
      }
  slReverse(&mdbNames);
  return mdbNames;
  }
  
  // ----------------- Validation and specialty APIs -----------------
  
  boolean mdbObjIsComposite(struct mdbObj *mdbObj)
  // returns TRUE if this is a valid composite object
  {
  char *objType = mdbObjFindValue(mdbObj,MDB_OBJ_TYPE);
  assert(objType != NULL);
  return sameWord(objType,MDB_OBJ_TYPE_COMPOSITE);
  }
  
  boolean mdbObjIsCompositeMember(struct mdbObj *mdbObj)
  // returns TRUE if this is a valid member of a composite.  DOES not confirm that composite obj exists
  {
  char *objType = mdbObjFindValue(mdbObj,MDB_OBJ_TYPE);
  assert(objType != NULL);
  if (differentWord(objType,MDB_OBJ_TYPE_TABLE) && differentWord(objType,MDB_OBJ_TYPE_FILE))
      return FALSE;
  return mdbObjContains(mdbObj,MDB_VAR_COMPOSITE,NULL);
  }
  
  int mdbObjsValidate(struct mdbObj *mdbObjs, boolean full)
  // Validates vars and vals against cv.ra.  Returns count of errors found.
  // Full considers vars not defined in cv as invalids
  {
  struct hash *termTypeHash = (struct hash *)cvTermTypeHash();
  struct mdbObj *mdbObj = NULL;
  int invalids = 0;
  for( mdbObj=mdbObjs; mdbObj!=NULL; mdbObj=mdbObj->next )
      {
      struct mdbVar *mdbVar = NULL;
      for(mdbVar = mdbObj->vars;mdbVar != NULL;mdbVar=mdbVar->next)
          {
          struct hash *termHash = hashFindVal(termTypeHash,mdbVar->var);
          if (termHash == NULL) // No cv definition for term so no validation can be done
              {
              if (!full)
                  continue;
              if (sameString(mdbVar->var,MDB_OBJ_TYPE)
              && (   sameString(mdbVar->val,MDB_OBJ_TYPE_TABLE)
                  || sameString(mdbVar->val,MDB_OBJ_TYPE_FILE)
                  || sameString(mdbVar->val,MDB_OBJ_TYPE_COMPOSITE)))
                  continue;
              printf("INVALID '%s' not defined in %s: %s -> %s = %s\n",mdbVar->var,CV_FILE_NAME,mdbObj->obj,mdbVar->var,mdbVar->val);
              invalids++;
              continue;
              }
          char *validationRule = hashFindVal(termHash,CV_VALIDATE);
          if (validationRule == NULL)
              {
              verbose(1,"ERROR in %s: Term '%s' in typeOfTerms but has no '%s' setting.\n",CV_FILE_NAME,mdbVar->var,CV_VALIDATE);
              continue;  // Should we errAbort?
              }
  
          // NOTE: Working on memory in hash but we are throwing away a comment and removing trailing spaces so that is okay
          strSwapChar(validationRule,'#','\0'); // Chop off any comment in the setting
          validationRule = trimSpaces(validationRule);
  
          // Validate should be or start with known word
          if (startsWithWord(CV_VALIDATE_CV,validationRule))
              {
              if (SETTING_NOT_ON(hashFindVal(termHash,CV_TOT_CV_DEFINED))) // Known type of term but no validation to be done
                  {
                  verbose(1,"ERROR in %s: Term '%s' says validate in cv but is not '%s'.\n",CV_FILE_NAME,mdbVar->var,CV_TOT_CV_DEFINED);
                  continue;
                  }
  
             // cvDefined so every val should be in cv
             struct hash *cvHashForTerm = (struct hash *)cvTermHash(mdbVar->var);
             if (cvHashForTerm == NULL)
                  {
                  verbose(1,"ERROR in %s: Term '%s' says validate in cv but not found as a cv term.\n",CV_FILE_NAME,mdbVar->var);
                  continue;
                  }
              if (hashFindVal(cvHashForTerm,mdbVar->val) == NULL) // No cv definition for term so no validation can be done
                  {
                  if (sameString(validationRule,CV_VALIDATE_CV_OR_NONE) && sameString(mdbVar->val,MDB_VAL_ENCODE_EDV_NONE))
                      continue;
                  else if (sameString(validationRule,CV_VALIDATE_CV_OR_CONTROL))
                      {
                      cvHashForTerm = (struct hash *)cvTermHash(CV_TERM_CONTROL);
                      if (cvHashForTerm == NULL)
                          {
                          verbose(1,"ERROR in %s: Term '%s' says validate in cv but not found as a cv term.\n",CV_FILE_NAME,CV_TERM_CONTROL);
                          continue;
                          }
                      if (hashFindVal(cvHashForTerm,mdbVar->val) != NULL)
                          continue;
                      }
                  printf("INVALID cv lookup: %s -> %s = %s\n",mdbObj->obj,mdbVar->var,mdbVar->val);
                  invalids++;
                  }
             }
          else if (startsWithWord(CV_VALIDATE_DATE,validationRule))
              {
              if (dateToSeconds(mdbVar->val,"%F") == 0)
                  {
                  printf("INVALID date: %s -> %s = %s\n",mdbObj->obj,mdbVar->var,mdbVar->val);
                  invalids++;
                  }
              }
          else if (startsWithWord(CV_VALIDATE_EXISTS,validationRule))
              continue;  // (e.g. fileName exists) Nothing to be done at this time.
          else if (startsWithWord(CV_VALIDATE_FLOAT,validationRule))
              {
              char* end;
              double notNeeded = strtod(mdbVar->val, &end); // Don't want float, just error (However, casting to void resulted in a comple error on Ubuntu Maveric and Lucid)
  
              if ((end == mdbVar->val) || (*end != '\0'))
                  {
                  printf("INVALID float: %s -> %s = %s (resulting double: %g)\n",mdbObj->obj,mdbVar->var,mdbVar->val,notNeeded);
                  invalids++;
                  }
              }
          else if (startsWithWord(CV_VALIDATE_INT,validationRule))
              {
              char *p0 = mdbVar->val;
              if (*p0 == '-')
                  p0++;
              char *p = p0;
              while ((*p >= '0') && (*p <= '9'))
                  p++;
              if ((*p != '\0') || (p == p0))
                  {
                  printf("INVALID integer: %s -> %s = %s\n",mdbObj->obj,mdbVar->var,mdbVar->val);
                  invalids++;
                  }
              }
          else if (startsWithWord(CV_VALIDATE_LIST,validationRule))
              {
              validationRule = skipBeyondDelimit(validationRule,' ');
              if (validationRule == NULL)
                  {
                  verbose(1,"ERROR in %s: Invalid '%s' for %s.\n",CV_FILE_NAME,CV_VALIDATE_LIST,mdbVar->var);
                  continue;
                  }
              int count = chopByChar(validationRule, ',', NULL, 0);
              if (count == 1)
                  {
                  if (differentString(mdbVar->val,validationRule))
                      {
                      printf("INVALID list '%s' match: %s -> %s = '%s'.\n",validationRule, mdbObj->obj,mdbVar->var,mdbVar->val);
                      invalids++;
                      }
                  }
              else if (count > 1)
                  {
                  char **array = needMem(count*sizeof(char*));
                  chopByChar(cloneString(validationRule), ',', array, count); // Want to also trimSpaces()? No
  
                  if (stringArrayIx(mdbVar->val, array, count) == -1)
                      {
                      printf("INVALID list '%s' match: %s -> %s = '%s'.\n",validationRule, mdbObj->obj,mdbVar->var,mdbVar->val);
                      invalids++;
                      }
                  }
              else
                  verbose(1,"ERROR in %s: Invalid 'validate list: %s' for term %s,\n",CV_FILE_NAME,validationRule,mdbVar->var);
              }
          else if (startsWithWord(CV_VALIDATE_NONE,validationRule))
              continue;
          else if (startsWithWord(CV_VALIDATE_REGEX,validationRule))
              {
              validationRule = skipBeyondDelimit(validationRule,' ');
              if (validationRule == NULL)
                  {
                  verbose(1,"ERROR in %s: Invalid '%s' for %s.\n",CV_FILE_NAME,CV_VALIDATE_REGEX,mdbVar->var);
                  continue;
                  }
              // Real work ahead interpreting regex
              regex_t regEx;
              int err = regcomp(&regEx, validationRule, REG_NOSUB);
              if(err != 0)  // Compile the regular expression so that it can be used.  Use: REG_EXTENDED ?
                  {
                  char buffer[128];
                  regerror(err, &regEx, buffer, sizeof buffer);
                  verbose(1,"ERROR in %s: Invalid regular expression for %s - %s.  %s\n",CV_FILE_NAME,mdbVar->var,validationRule,buffer);
                  continue;
                  }
              err = regexec(&regEx, mdbVar->val, 0, NULL, 0);
              if (err != 0)
                  {
                  //char buffer[128];
                  //regerror(err, &regEx, buffer, sizeof buffer);
                  printf("INVALID regex '%s' match: %s -> %s = '%s'.\n",validationRule, mdbObj->obj,mdbVar->var,mdbVar->val);
                  invalids++;
                  }
              regfree(&regEx);
              }
          else
              verbose(1,"ERROR in %s: Unknown validationRule rule '%s' for term %s.\n",CV_FILE_NAME,validationRule,mdbVar->var);
          }
      }
  return invalids;
  }
  
  static struct slName *mdbObjGetNamedEncodeEdvs(struct mdbObj *compObj)
  // returns NULL or the list of EDVs defined for this composite
  {
  char *edvs = mdbObjFindValue(compObj,MDB_VAR_ENCODE_EDVS);
  if (edvs == NULL)
      return NULL;
  
  edvs = cloneString(edvs);
  if (strchr(     edvs,',') != NULL) // Tolerate delimit by commas
      strSwapChar(edvs,',',' ');
  else if (strchr(edvs,';') != NULL) // Tolerate delimit by semicolons
      strSwapChar(edvs,';',' ');
  
  struct slName *compositeEdvs = slNameListFromString(edvs,' ');
  freeMem(edvs);
  return compositeEdvs;
  }
  
  static struct mdbVar *mdbObjEncodeEdvsAsMdbVars(struct mdbObj *mdbObj,struct slName *compositeEdvs,boolean includeNone)
  // returns the EDVs and values for the composite member object
  // If includeNone, then defined variables not found in obj will be included as {var}="None".
  {
  struct mdbVar *edvVars = NULL;
  struct slName *var = compositeEdvs;
  for(;var!=NULL;var=var->next)
      {
      char *val = mdbObjFindValue(mdbObj,var->name);
      if (val)
          mdbVarAdd(&edvVars, var->name,val);
      else if (includeNone)
          {
          if (differentWord(var->name,ENCODE_EXP_FIELD_ORGANISM)) // Does not go into EDV's sent to encodeExp table
              mdbVarAdd(&edvVars, var->name,MDB_VAL_ENCODE_EDV_NONE);
          }
      }
  slReverse(&edvVars);
  return edvVars;
  }
  
  struct slName *mdbObjFindCompositeNamedEncodeEdvs(struct sqlConnection *conn,char *tableName,struct mdbObj *mdbObj)
  // returns NULL or the Experiment Defining Variable names for this composite
  {
  if (!mdbObjIsCompositeMember(mdbObj))
      return NULL; // This should be a valid composite memeber
  
  struct mdbObj *compObj = mdbObjQueryCompositeObj(conn,tableName,mdbObj);
  if (compObj == NULL)
      return NULL;
  struct slName *edvs = mdbObjGetNamedEncodeEdvs(compObj);
  mdbObjFree(&compObj);
  return edvs;
  }
  
  struct mdbVar *mdbObjFindEncodeEdvPairs(struct sqlConnection *conn,char *tableName,struct mdbObj *mdbObj,boolean includeNone)
  // returns NULL or the Experiment Defining Variables and values for this composite member object
  // If includeNone, then defined variables not found in obj will be included as {var}="None".
  {
  // In rare cases, the EDVs reside with the object and NOT in a objType=composite.
  struct slName *compositeEdvs = mdbObjGetNamedEncodeEdvs(mdbObj);  // looking locally first.
  if (compositeEdvs == NULL)
      {
      compositeEdvs = mdbObjFindCompositeNamedEncodeEdvs(conn,tableName,mdbObj);
      if (compositeEdvs == NULL)
          return NULL;
      }
  
  return mdbObjEncodeEdvsAsMdbVars(mdbObj,compositeEdvs,includeNone);
  }
  
  struct mdbObj *mdbObjsEncodeExperimentify(struct sqlConnection *conn,char *db,char *tableName,char *expTable,
                         struct mdbObj **pMdbObjs,int warn,boolean createExpIfNecessary,boolean updateAccession)
  // Organizes objects into experiments and validates experiment IDs.  Will add/update the ids in the structures.
  // If warn=1, then prints to stdout all the experiments/obs with missing or wrong expIds;
  //    warn=2, then print line for each obj with expId or warning.
  // createExpIfNecessary means add expId to encodeExp table. updateAccession too if necessary.
  // Returns a new set of mdbObjs that is what can (and should) be used to update the mdb via mdbObjsSetToDb().
  {
  // Here is what "experimentify" does from "mdbPrint -encodeExp" and "mdbUpdate -encodeExp":
  //    - Uses normal selection methods to get a set of objects (e.g. one composite worth) or all objs. (in mdbPrint and mdbUpdate)
  //    - This API:
  //    - Breaks up and walks through set of objects composite by composite
  //    - Looks up EDVs (Experiment Defining Variables) for composite.
  //        These are defined in the mdb under objType=composite expVars=
  //    - Breaks up and walks through composite objects exp by exp as defined by EDVs
  //    - Uses encodeExp API to determine what expId should be.
  //    - Creates new mdbObjs list of updates needed to put expId and dccAccession into the mdb.
  //    - From "mdbPrint", this API warns of mismatches or missing expIds
  //    - From "mdbUpdate" (not -test) then that utility will update the mdb from this API's return structs.  If -test, will reveal what would be updated.
  //    - FIXME: Need to extend this to update dccAccession separately from expId.
  
  if (pMdbObjs == NULL || *pMdbObjs == NULL)
      return 0;
  struct mdbObj *mdbObjs = *pMdbObjs;
  struct mdbObj *mdbProcessedObs = NULL;
  struct mdbObj *mdbUpdateObjs = NULL;
  if (expTable == NULL)
      expTable = ENCODE_EXP_TABLE;
  
  verbose(2, "mdbObjsEncodeExperimentify() beginning for %d objects.\n",slCount(*pMdbObjs));
  // Sort all objects by composite, so that we handle composite by composite
  mdbObjsSortOnVars(&mdbObjs, MDB_VAR_COMPOSITE);
  
  struct dyString *dyVars = dyStringNew(256);
  
  while(mdbObjs != NULL)
      {
      // Work on a composite at a time
      boolean compositelessObj = FALSE;
      char *compName = NULL;
      while(mdbObjs != NULL && compName == NULL)
          {
          compName = mdbObjFindValue(mdbObjs,MDB_VAR_COMPOSITE);
          if (compName == NULL)
              {
              if (mdbObjFindValue(mdbObjs,MDB_VAR_ENCODE_EDVS) == NULL)
                  {
                  verbose(1, "Object '%s' has no %s or %s defined.\n",mdbObjs->obj,MDB_VAR_COMPOSITE,MDB_VAR_ENCODE_EDVS);
                  mdbProcessedObs = slCat(mdbProcessedObs,slPopHead(&mdbObjs));
                  continue;
                  }
              verbose(2, "mdbObjsEncodeExperimentify() starting on compositeless set.\n");
              break;
              }
          }
      struct mdbObj *mdbCompositeObjs = NULL;
      if (compName != NULL)
          mdbCompositeObjs = mdbObjsFilter(&mdbObjs, MDB_VAR_COMPOSITE, compName,TRUE);
      else
          mdbCompositeObjs = slPopHead(&mdbObjs); // Rare cases there is no composite set.
      assert(mdbCompositeObjs != NULL);
      // --- At this point we have nibbled off a composite worth of objects from the full set of objects
  
      // Find the composite obj if it exists
      struct mdbObj *compObj = NULL;
      if (compName != NULL)
          {
          compObj =mdbObjsFilter(&mdbCompositeObjs, MDB_OBJ_TYPE, MDB_OBJ_TYPE_COMPOSITE,TRUE);
          if (compObj == NULL) // May be NULL if mdbObjs passed in was produced by too narrow of selection criteria
              {
              compObj = mdbObjQueryCompositeObj(conn,tableName,mdbCompositeObjs);  // First obj on list will do
              if(compObj == NULL)  // This should be assertable
                  {
                  verbose(1, "Composite '%s' has not been defined.\n",compName);
                  mdbProcessedObs = slCat(mdbProcessedObs,mdbCompositeObjs);
                  mdbCompositeObjs = NULL;
                  continue;
                  }
              }
          else
              slAddHead(&mdbProcessedObs,compObj); // We can still use the pointer, but will not "process" it.  NOTE: leak the queried one
          }
      else
          {
          compObj = mdbCompositeObjs;  // Should be only one
          compName = mdbCompositeObjs->obj;
          compositelessObj = TRUE;
          }
      verbose(2, "mdbObjsEncodeExperimentify() working on %s %s%s.\n",compName,MDB_VAR_COMPOSITE,(compositelessObj?"less set":""));
  
      // Obtain experiment defining variables for the composite (or compositeless obj)
      struct slName *compositeEdvs = mdbObjGetNamedEncodeEdvs(compObj);
      if (compositeEdvs == NULL)
          {
          verbose(1, "There are no experiment defining variables established for this %s%s.  Add them to obj %s => var:%s.\n",
                  MDB_VAR_COMPOSITE,(compositelessObj?"less set":""), compName,MDB_VAR_ENCODE_EDVS);
          mdbProcessedObs = slCat(mdbProcessedObs,mdbCompositeObjs);
          mdbCompositeObjs = NULL;
          continue;
          }
      dyStringClear(dyVars);
      dyStringAppend(dyVars,slNameListToString(compositeEdvs, ' '));
  
      if (warn > 0)
          printf("Composite%s '%s' with %d objects has %d EDVs(%s): [%s].\n",(compositelessObj?"less set":""),compName,
                 slCount(mdbCompositeObjs),slCount(compositeEdvs),MDB_VAR_ENCODE_EDVS,dyStringContents(dyVars)); // Set the stage
  
      // Organize composite objs by EDVs
      dyStringPrintf(dyVars, " %s %s ",MDB_VAR_VIEW,MDB_VAR_REPLICATE); // Allows for nicer sorted list
      char *edvSortOrder = cloneString(dyStringContents(dyVars));
  
      // Walk through objs for an exp as defined by EDVs
      int expCount=0;     // Count of experiments in composite
      int expMissing=0;   // Count of objects with missing expId
      int accMissing=0;   // Count of objects with missing accessions
      int expObjsCount=0; // Total of all experimental object accoss the composite
      int expMax=0;       // Largest experiment (in number of objects)
      int expMin=999;     // Smallest experiment (in number of objects)
      while(mdbCompositeObjs != NULL)
          {
          // Must sort each cycle, because sort order is lost during mdbObjs FilterByVars();
          mdbObjsSortOnVars(&mdbCompositeObjs, edvSortOrder);
  
          // Get the EDVs for the first obj
          struct mdbVar *edvVarVals = mdbObjEncodeEdvsAsMdbVars(mdbCompositeObjs,compositeEdvs,TRUE); // use first obj on list and include Nones
          if (edvVarVals == NULL)
              {
              verbose(1, "There are no experiment defining variables for this object '%s'.\n",mdbCompositeObjs->obj);
              slAddHead(&mdbProcessedObs,slPopHead(&mdbCompositeObjs)); // We're done with this one
              continue;
              }
  
          // Construct the var=val string for filtering a single exp (set of objs) from composite worth of objs
          dyStringClear(dyVars);
          struct mdbVar *edvVar = edvVarVals;
          int valsFound = 0;
          for(;edvVar!=NULL;edvVar=edvVar->next)
              {
              dyStringPrintf(dyVars,"%s=%s ",edvVar->var,edvVar->val);
              if (differentString(edvVar->val,MDB_VAL_ENCODE_EDV_NONE))
                  valsFound++;
              }
          dyStringContents(dyVars)[dyStringLen(dyVars) -1] = '\0'; // Nicer printing is all
  
          if (valsFound == 0)
              {
              verbose(1, "There are no experiment defining variables for this object '%s'.\n",mdbCompositeObjs->obj);
              slAddHead(&mdbProcessedObs,slPopHead(&mdbCompositeObjs)); // We're done with this one
              mdbVarsFree(&edvVarVals);
              continue;
              }
  
          // Work on one experiment at a time
          verbose(2, "mdbObjsEncodeExperimentify() working on EDVs: %s.\n",dyStringContents(dyVars));
          struct mdbObj *mdbExpObjs = mdbObjsFilterByVars(&mdbCompositeObjs,dyStringContents(dyVars),TRUE,TRUE); // None={notFound}
  
          // --- At this point we have nibbled off an experiment worth of objects from the composite set of objects
  
          int objsInExp = slCount(mdbExpObjs);
          assert(objsInExp > 0);
          expCount++;
          expObjsCount += objsInExp; // Total of all experimental objects across the composite
  
          // Look up each exp in EXPERIMENTS_TABLE
          char experimentId[128];
          int expId = ENCODE_EXP_IX_UNDEFINED;
          struct encodeExp *exp = encodeExpGetByMdbVarsFromTable(db, edvVarVals, expTable);
          // --------- BLOCK creation of expIds, at least during rollout of encodeExp
          // BLOCKED if (exp == NULL && createExpIfNecessary)
          // BLOCKED     exp = encodeExpGetOrCreateByMdbVarsFromTable(db, edvVarVals, expTable);
          // --------- BLOCK creation of expIds, at least during rollout of encodeExp
          mdbVarsFree(&edvVarVals); // No longer needed
  
          // Make sure the accession is set if requested.
          if (createExpIfNecessary && updateAccession
          && exp->ix != ENCODE_EXP_IX_UNDEFINED && exp->accession == NULL)
              encodeExpSetAccession(exp, expTable);
  
          if (exp != NULL)
              expId = exp->ix;
  
          if (expId == ENCODE_EXP_IX_UNDEFINED)
              {
              safef(experimentId,sizeof(experimentId),"{missing}");
              if (warn > 0)
                  printf("Experiment %s EDV: [%s] is not defined in %s.%s table.\n",experimentId,dyStringContents(dyVars), ENCODE_EXP_DATABASE, expTable);
                  //printf("Experiment %s EDV: [%s] is not defined in %s table. Remaining:%d and %d\n",experimentId,dyStringContents(dyVars),EXPERIMENTS_TABLE,slCount(mdbCompositeObjs),slCount(mdbObjs));
              if (warn < 2) // From mdbUpdate (warn=1), just interested in testing waters.  From mdbPrint (warn=2) list all objs in exp.
                  {
                  expMissing += slCount(mdbExpObjs);
                  mdbProcessedObs = slCat(mdbProcessedObs,mdbExpObjs);
                  mdbExpObjs = NULL;
                  encodeExpFree(&exp);
                  continue;
                  }
              }
          else
              {
              safef(experimentId,sizeof(experimentId),"%d",expId);
              if (warn > 0)
                  printf("Experiment %s has %d objects based upon %d EDVs: [%s].\n",experimentId,slCount(mdbExpObjs),valsFound,dyStringContents(dyVars)); // Set the stage
              }
  
          // Now we can walk through each obj in experiment and determine if it has the correct expId
          int foundId = FALSE;
          int errors = objsInExp;
          if (expMax < objsInExp)
              expMax = objsInExp;
          if (expMin > objsInExp)
              expMin = objsInExp;
          while(mdbExpObjs != NULL)
              {
              struct mdbObj *obj = slPopHead(&mdbExpObjs);
  
              { // NOTE: This list could expand but we expect only tables and files to be objs in an experiment
                  char *objType = mdbObjFindValue(obj,MDB_OBJ_TYPE);
                  assert(objType != NULL && (sameString(objType,MDB_OBJ_TYPE_TABLE) || sameString(objType,MDB_OBJ_TYPE_FILE)));
                  }
  
              boolean updateObj = FALSE;
              char *val = mdbObjFindValue(obj,MDB_VAR_ENCODE_EXP_ID);
              if (val != NULL)
                  {
                  foundId = TRUE; // warn==1 will give only 1 exp wide error if no individual errors.  NOTE: would be nice if those with expId sorted to beginning, but can't have everything.
                  int thisId = atoi(val);
                  if (expId == ENCODE_EXP_IX_UNDEFINED || thisId != expId)
                      {
                      updateObj = TRUE;  // Always an error!
                      expMissing++;
  
                      printf("    ERROR  %s %-60s has bad %s=%s.\n",experimentId,obj->obj,MDB_VAR_ENCODE_EXP_ID,val);
                      }
                  else
                      {
-                     char *acc = mdbObjFindValue(obj,MDB_VAR_DCC_ACCESSION); // FIXME: Add code to update accession to encodeExp
-                     if (updateAccession && !createExpIfNecessary && exp->accession == NULL)
+                     char *acc = mdbObjFindValue(obj,MDB_VAR_DCC_ACCESSION);
+                     if (updateAccession && !createExpIfNecessary && exp->accession == NULL) // -test so one wasn't created
                          {
                          exp->accession = needMem(16);
                          safef(exp->accession, 16, "TEMP%06d", exp->ix); // Temporary since this is not an update but we want -test to work.
                          }
                      if (exp->accession != NULL && (acc == NULL || differentString(acc,exp->accession)))
                          {
-                         updateObj = TRUE;
+                         if (updateAccession)
+                             updateObj = TRUE;
+ 
                          accMissing++;
  
                          if (acc != NULL) // Always an error
                              printf("    ERROR  %s %-60s %s set, has wrong %s: %s.\n",experimentId,obj->obj,
                                      MDB_VAR_ENCODE_EXP_ID,MDB_VAR_DCC_ACCESSION,acc);
                          else if (warn > 1)           // NOTE: Could give more info for each obj as per wrangler's desires
                              printf("           %s %-60s %s set, needs %s.\n",experimentId,obj->obj,MDB_VAR_ENCODE_EXP_ID,MDB_VAR_DCC_ACCESSION);
                          }
                      else
                          {
                          errors--;       // One less error
                          if (warn > 1)           // NOTE: Could give more info for each obj as per wrangler's desires
                              printf("           %s %-60s %s\n",experimentId,obj->obj,(exp->accession != NULL ? exp->accession : ""));
                          }
                      }
                  }
              else
                  {
                  if (expId != ENCODE_EXP_IX_UNDEFINED)
                      {
                      updateObj = TRUE;
                      expMissing++;
                      }
  
                  if ((foundId && warn > 0) || warn > 1)
                      {
                      if (updateObj)
                          printf("           %s %-60s needs updating to mdb.\n",experimentId,obj->obj);
                      else
                          printf("           %s %s\n",experimentId,obj->obj); // missing
                      }
                  }
  
              // This object needs to be updated.
              if (updateObj)
                  {
                  mdbObjSetVarInt(obj,MDB_VAR_ENCODE_EXP_ID,expId);
                  struct mdbObj *newObj = mdbObjCreate(obj->obj,MDB_VAR_ENCODE_EXP_ID, experimentId);
                  assert(exp != NULL);
-                 if (exp->accession != NULL)
+                 if (exp->accession != NULL && updateAccession)
                      mdbObjSetVar(newObj,MDB_VAR_DCC_ACCESSION,exp->accession);
                  slAddHead(&mdbUpdateObjs,newObj);
                  }
              slAddHead(&mdbProcessedObs,obj);
              }
          // Done with one experiment
          encodeExpFree(&exp);
  
          if (!foundId && errors > 0 && warn > 0)
              printf("           %s all %d objects are missing an %s.\n",experimentId,objsInExp,MDB_VAR_ENCODE_EXP_ID);
          }
      // Done with one composite
  
      if (expCount > 0)
          {
          printf("Composite%s '%s' has %d recognizable experiment%s with %d objects needing %s",
                 (compositelessObj?"less set":""),compName,expCount,(expCount != 1?"s":""),expMissing,MDB_VAR_ENCODE_EXP_ID);
          if (accMissing > 0)
              printf(" and %d objects needing %s",accMissing,MDB_VAR_DCC_ACCESSION);
          printf(" updated.\n   objects/experiment: min:%d  max:%d  mean:%lf.\n",expMin,expMax,((double)expObjsCount/expCount));
          }
  
      if (edvSortOrder != NULL)
          freeMem(edvSortOrder);
      slNameFreeList(compositeEdvs);
      }
  // Done with all composites
  
  dyStringFree(&dyVars);
  
  *pMdbObjs = mdbProcessedObs;
  
  return mdbUpdateObjs;
  }
  
  boolean mdbObjIsEncode(struct mdbObj *mdb)
  // Return true if this metaDb object is for ENCODE
  {
  return mdbObjContains(mdb, MDB_VAR_PROJECT, MDB_VAL_ENCODE_PROJECT);
  
  // Could be more stringent:
  //return (   mdbObjContains(mdbObj, MDB_VAR_LAB,         NULL)
  //        && mdbObjContains(mdbObj, MDB_VAR_DATATYPE,    NULL)
  //        && mdbObjContains(mdbObj, MDB_VAR_ENCODE_SUBID,NULL));
  }
  
  boolean mdbObjInComposite(struct mdbObj *mdb, char *composite)
  // Return true if metaDb object is in specified composite.
  // If composite is NULL, always return true
  {
  if (composite == NULL || sameOk(composite, mdbObjFindValue(mdb, MDB_VAR_COMPOSITE)))
      return TRUE;
  return FALSE;
  }
  
  // --------------- Free at last ----------------
  void mdbObjsFree(struct mdbObj **mdbObjsPtr)
  // Frees one or more metadata objects and any contained mdbVars.  Will free any hashes as well.
  {
  
  if(mdbObjsPtr != NULL && *mdbObjsPtr != NULL)
      {
      // free all roots
      struct mdbObj *mdbObj = NULL;
      while((mdbObj = slPopHead(mdbObjsPtr)) != NULL)
          {
          // Free hash first (shared memory)
          hashFree(&(mdbObj->varHash));
  
          // free all leaves
          mdbVarsFree(&(mdbObj->vars));
  
          // The rest of root
          freeMem(mdbObj->obj);
          freeMem(mdbObj);
          }
      freez(mdbObjsPtr);
      }
  }
  
  void mdbVarsFree(struct mdbVar **mdbVarsPtr)
  // Frees one or more metadata vars and any val as well
  {
  struct mdbVar *mdbVar = NULL;
  while((mdbVar = slPopHead(mdbVarsPtr)) != NULL)
      {
      freeMem(mdbVar->val);
      freeMem(mdbVar->var);
      freez(&mdbVar);
      }
  }
  
  void mdbByVarsFree(struct mdbByVar **mdbByVarsPtr)
  // Frees one or more metadata vars and any contained vals and objs.  Will free any hashes as well.
  {
  if(mdbByVarsPtr != NULL && *mdbByVarsPtr != NULL)
      {
      // free all roots
      struct mdbByVar *rootVar = NULL;
      while((rootVar = slPopHead(mdbByVarsPtr)) != NULL)
          {
          // Free hash first (shared memory)
          hashFree(&(rootVar->valHash));
  
          // free all limbs
          struct mdbLimbVal *limbVal = NULL;
          while((limbVal = slPopHead(&(rootVar->vals))) != NULL)
              mdbLimbValFree(&limbVal);
  
          // The rest of root
          if(rootVar->var)
              freeMem(rootVar->var);
          freeMem(rootVar);
          }
      freez(mdbByVarsPtr);
      }
  }
  
  
  
  // ----------------- CGI specific routines for use with tdb -----------------
  #define MDB_NOT_FOUND ((struct mdbObj *)-666)
  #define METADATA_NOT_FOUND ((struct mdbObj *)-999)
  #define MDB_OBJ_KEY "mdbObj"
  static struct mdbObj *metadataForTableFromTdb(struct trackDb *tdb)
  // Returns the metadata for a table from a tdb setting.
  {
  char *setting = trackDbSetting(tdb, MDB_METADATA_KEY);
  if(setting == NULL)
      return NULL;
  struct mdbObj *mdbObj;
  AllocVar(mdbObj);
  mdbObj->obj     = cloneString(tdb->table);
  AllocVar(mdbObj->vars);
  mdbObj->vars->var = cloneString(MDB_OBJ_TYPE);
  mdbObj->vars->val = cloneString(MDB_OBJ_TYPE_TABLE);
  mdbObj->varHash = hashNew(0);
  hashAdd(mdbObj->varHash, mdbObj->vars->var, mdbObj->vars);
  mdbObj = mdbObjAddVarPairs(mdbObj,setting);
  mdbObjRemoveVars(mdbObj,MDB_VAR_TABLENAME); // NOTE: Special hint that the tdb metadata is used since no mdb metadata is found
  return mdbObj;
  }
  
  const struct mdbObj *metadataForTable(char *db,struct trackDb *tdb,char *table)
  // Returns the metadata for a table.  NEVER FREE THIS STRUCT!
  {
  struct mdbObj *mdbObj = NULL;
  
  // See of the mdbObj was already built
  if(tdb != NULL)
      {
      mdbObj = tdbExtrasGetOrDefault(tdb, MDB_OBJ_KEY,NULL);
      if(mdbObj == METADATA_NOT_FOUND) // NOT in mtatbl, not in tdb metadata setting!
          return NULL;
      else if(mdbObj == MDB_NOT_FOUND) // looked mdb already and not found!
          return metadataForTableFromTdb(tdb);
      else if(mdbObj != NULL)
          {
          return mdbObj;  // No reason to query the table again!
          }
      }
  
  struct sqlConnection *conn = hAllocConn(db);
  char *mdb = mdbTableName(conn,TRUE);  // Look for sandbox name first
  if(tdb != NULL && tdb->table != NULL)
      table = tdb->table;
  if(mdb != NULL)
      mdbObj = mdbObjQueryByObj(conn,mdb,table,NULL);
  hFreeConn(&conn);
  
  // save the mdbObj for next time
  if(tdb)
      {
      if(mdbObj != NULL)
          tdbExtrasAddOrUpdate(tdb,MDB_OBJ_KEY,mdbObj);
      else
          {
          tdbExtrasAddOrUpdate(tdb,MDB_OBJ_KEY,MDB_NOT_FOUND);
          return metadataForTableFromTdb(tdb);  // FIXME: metadata setting in TDB is soon to be obsolete
          }
      }
  
  return mdbObj;
  }
  
  const char *metadataFindValue(struct trackDb *tdb, char *var)
  // Finds the val associated with the var or retruns NULL
  {
  struct mdbObj *mdbObj = tdbExtrasGetOrDefault(tdb, MDB_OBJ_KEY,NULL);
  if(mdbObj == MDB_NOT_FOUND) // Note, only we if already looked for mdb (which requires db)
      mdbObj = metadataForTableFromTdb(tdb);
  if (mdbObj == NULL || mdbObj == METADATA_NOT_FOUND)
      return NULL;
  
  return mdbObjFindValue(mdbObj,var);
  }
  
  
  struct mdbObj *mdbObjSearch(struct sqlConnection *conn, char *var, char *val, char *op, int limit)
  // Search the metaDb table for objs by var and val.  Can restrict by op "is", "like", "in" and accept (non-zero) limited string size
  // Search is via mysql, so it's case-insensitive.  Return is sorted on obj.
  {
  if (var == NULL && val == NULL)
      errAbort("mdbObjSearch requests objects but provides no criteria.\n");
  
  char *tableName = mdbTableName(conn,TRUE); // Look for sandBox name first
  
  // Build a query string
  struct dyString *dyQuery = dyStringNew(512);
  dyStringPrintf(dyQuery,"select l1.obj, l1.var, l1.val from %s l1",tableName);
  
  if (var != NULL || val != NULL)
      dyStringPrintf(dyQuery," where exists (select l2.obj from %s l2 where l2.obj = l1.obj and ",tableName);
  if(var != NULL)
      dyStringPrintf(dyQuery,"l2.var = '%s'", var);
  if(var != NULL && val != NULL)
      dyStringAppend(dyQuery," and ");
  if(val != NULL)
      {
      dyStringAppend(dyQuery,"l2.val ");
      if(sameString(op, "in"))
          dyStringPrintf(dyQuery,"in (%s)", val); // Note, must be a formatted string already: 'a','b','c' or  1,2,3
      else if(sameString(op, "contains") || sameString(op, "like"))
          dyStringPrintf(dyQuery,"like '%%%s%%'", val);
      else if (limit > 0 && strlen(val) != limit)
          dyStringPrintf(dyQuery,"like '%.*s%%'", limit, val);
      else
          dyStringPrintf(dyQuery,"= '%s'", val);
      }
  dyStringAppendC(dyQuery,')');
  dyStringAppend(dyQuery," order by obj");
  
  struct mdb *mdb = mdbLoadByQuery(conn, dyStringCannibalize(&dyQuery));
  verbose(3, "rows (vars) returned: %d\n",slCount(mdb));
  struct mdbObj *mdbObjs = mdbObjsLoadFromMemory(&mdb,TRUE);
  
  return mdbObjs;
  }
  
  struct mdbObj *mdbObjRepeatedSearch(struct sqlConnection *conn,struct slPair *varValPairs,boolean tables,boolean files)
  // Search the metaDb table for objs by var,val pairs.  Uses mdbCvSearchMethod() if available.
  // This method will use mdbObjsQueryByVars()
  {
  struct slPair *onePair;
  struct dyString *dyTerms = dyStringNew(256);
  // Build list of terms as "var1=val1 var2=val2a,val2b,val2c var3=%val3%"
  for(onePair = varValPairs; onePair != NULL; onePair = onePair->next)
      {
      if (isEmpty(((char *)(onePair->val)))) // NOTE: All the parens are needed to get the macro to do the right thing
          continue;
      enum cvSearchable searchBy = cvSearchMethod(onePair->name);
      if (searchBy == cvSearchBySingleSelect || searchBy == cvSearchByMultiSelect)  // multiSelect val will be filled with a comma delimited list
          {
          if (strchr((char *)onePair->val,' '))
              dyStringPrintf(dyTerms,"%s=\"%s\" ",onePair->name,(char *)onePair->val);
          else
              dyStringPrintf(dyTerms,"%s=%s ",onePair->name,(char *)onePair->val);
          }
      else if (searchBy == cvSearchByFreeText)                                      // If select is by free text then like
          dyStringPrintf(dyTerms,"%s=%%%s%% ",onePair->name,(char *)onePair->val);
      else if (sameWord(onePair->name,MDB_VAR_COMPOSITE))  // special case.  Not directly searchable by UI but indirectly and will show up here.
          dyStringPrintf(dyTerms,"%s=%s ",onePair->name,(char *)onePair->val);
      else if (searchBy == cvSearchByDateRange || searchBy == cvSearchByIntegerRange)
          {
          // TO BE IMPLEMENTED
          }
      }
  // Be sure to include table or file in selections
  if (tables)
      dyStringPrintf(dyTerms,"%s=%s ",MDB_OBJ_TYPE,MDB_OBJ_TYPE_TABLE);
  if (files)
      dyStringPrintf(dyTerms,"%s=? ",MDB_VAR_FILENAME);
  
  // Build the mdbByVals struct and then select all mdbObjs in one query
  struct mdbObj *mdbObjs = mdbObjsQueryByVarVals(conn,dyStringContents(dyTerms));
  dyStringFree(&dyTerms);
  return mdbObjs;
  }
  
  struct slName *mdbObjNameSearch(struct sqlConnection *conn, char *var, char *val, char *op, int limit, boolean tables, boolean files)
  // Search the metaDb table for objs by var and val.  Can restrict by op "is", "like", "in" and accept (non-zero) limited string size
  // Search is via mysql, so it's case-insensitive.  Return is sorted on obj.
  {  // Note: This proves faster than getting mdbObjs then converting to slNames
  struct mdbObj *mdbObjs = mdbObjSearch(conn,var,val,op,limit);
  
  // May only be interested in tables or files:
  if (tables || files)
      {
      struct mdbObj *mdbObjsDropped = mdbObjsFilterTablesOrFiles(&mdbObjs,tables,files);
      mdbObjsFree(&mdbObjsDropped);
      }
  
  struct slName *mdbNames = mdbObjToSlName(mdbObjs);
  mdbObjsFree(&mdbObjs);
  return mdbNames;
  }
  
  static void mdbSearchableQueryRestictForTablesOrFiles(struct dyString *dyQuery,char *tableName, char letter,boolean hasTableName, boolean hasFileName)
  // Append table and file restrictions onto an mdb query.
  // letter (e.g. 'A') should be used in original query to alias table name: "select A.val from metaDb A where A.val = 'fred'".
  {
  // A note about tables and files: objType=table may have fileNames associated, but objType=file will not have tableNames
  // While objType=table should have a var=tableName, this is redundant because the obj=tableName
  // So the lopsided 'exists' queries below are meant to be the most flexible/efficent
  
  assert(isalpha(letter) && isalpha(letter + 2)); // will need one or two sub-queries.
  char nextLtr = letter + 1;
  
  // We are only searching for objects that are of objType table or file. objType=composite are not search targets!
  if (hasTableName && !hasFileName) // objType=table may have fileNames associated, but objType=file will not have tableNames
      dyStringPrintf(dyQuery," and exists (select %c.obj from %s %c where %c.obj = %c.obj and %c.var='objType' and %c.val = '%s')",
                      nextLtr,tableName,nextLtr,nextLtr,letter,nextLtr,nextLtr,MDB_OBJ_TYPE_TABLE);
  else // tables OR files (but not objType=composite)
      dyStringPrintf(dyQuery," and exists (select %c.obj from %s %c where %c.obj = %c.obj and %c.var='objType' and %c.val in ('%s','%s'))",
                      nextLtr,tableName,nextLtr,nextLtr,letter,nextLtr,nextLtr,MDB_OBJ_TYPE_TABLE,MDB_OBJ_TYPE_FILE);
  
  nextLtr++;
  
  if (!hasTableName && hasFileName) // last of 3 possibilites objType either table or file but must have fileName var
      dyStringPrintf(dyQuery," and exists (select %c.obj from %s %c where %c.obj = %c.obj and %c.var in ('%s','%s'))",
                     nextLtr,tableName,nextLtr,nextLtr,letter,nextLtr,MDB_VAR_FILENAME,MDB_VAR_FILEINDEX);
  }
  
  struct slName *mdbValSearch(struct sqlConnection *conn, char *var, int limit, boolean hasTableName, boolean hasFileName)
  // Search the metaDb table for vals by var.  Can impose (non-zero) limit on returned string size of val
  // Search is via mysql, so it's case-insensitive.  Return is sorted on val.
  // Searchable vars are only for table or file objects.  Further restrict to vars associated with tableName, fileName or both.
  {  // TODO: Change this to use normal mdb struct routines?
  struct slName *retVal;
  
  if (!hasTableName && !hasFileName)
      errAbort("mdbValSearch requests vals associated with neither table nor files.\n");
  
  char *tableName = mdbTableName(conn,TRUE); // Look for sandBox name first
  
  char letter = 'A';
  struct dyString *dyQuery = dyStringNew(512);
  if (limit > 0)
      dyStringPrintf(dyQuery,"select distinct LEFT(%c.val,%d)",letter,limit);
  else
      dyStringPrintf(dyQuery,"select distinct %c.val",letter);
  
  dyStringPrintf(dyQuery," from %s %c where %c.var='%s'",tableName,letter,letter,var);
  
  mdbSearchableQueryRestictForTablesOrFiles(dyQuery,tableName, letter, hasTableName, hasFileName);
  
  dyStringPrintf(dyQuery," order by %c.val",letter);
  
  retVal = sqlQuickList(conn, dyStringCannibalize(&dyQuery));
  slNameSortCase(&retVal);
  return retVal;
  }
  
  struct slPair *mdbValLabelSearch(struct sqlConnection *conn, char *var, int limit, boolean tags, boolean hasTableName, boolean hasFileName)
  // Search the metaDb table for vals by var and returns val (as pair->name) and controlled vocabulary (cv) label
  // (if it exists) (as pair->val).  Can impose (non-zero) limit on returned string size of name.
  // Searchable vars are only for table or file objects.  Further restrict to vars associated with tableName, fileName or both.
  // Return is case insensitive sorted on label (cv label or else val). If requested, return cv tag instead of mdb val.
  {  // TODO: Change this to use normal mdb struct routines?
  if (!hasTableName && !hasFileName)
      errAbort("mdbValLabelSearch requests vals associated with neither table nor files.\n");
  
  char *tableName = mdbTableName(conn,TRUE); // Look for sandBox name first
  
  char letter = 'A';
  struct dyString *dyQuery = dyStringNew(512);
  if (limit > 0)
      dyStringPrintf(dyQuery,"select distinct LEFT(%c.val,%d)",letter,limit);
  else
      dyStringPrintf(dyQuery,"select distinct %c.val",letter);
  
  dyStringPrintf(dyQuery," from %s %c where %c.var='%s'",tableName,letter,letter,var);
  
  mdbSearchableQueryRestictForTablesOrFiles(dyQuery,tableName,letter, hasTableName, hasFileName);
  //warn("%s",dyStringContents(dyQuery));
  
  struct hash *varHash = (struct hash *)cvTermHash(var);
  
  struct slPair *pairs = NULL;
  struct sqlResult *sr = sqlGetResult(conn, dyStringContents(dyQuery));
  dyStringFree(&dyQuery);
  char **row;
  while ((row = sqlNextRow(sr)) != NULL)
      {
      char *val = row[0];
      char *label = NULL;
      if (varHash != NULL)
          {
          struct hash *valHash = hashFindVal(varHash,val);
          if (valHash != NULL)
              {
              label = cloneString(hashOptionalVal(valHash,CV_LABEL,row[0]));
              if (tags)
                  {
                  char *tag = hashFindVal(valHash,CV_TAG);
                  if (tag != NULL)
                      val = tag;
                  }
              }
          }
      if (label == NULL);
          label = cloneString(row[0]);
      label = strSwapChar(label,'_',' ');  // vestigial _ meaning space
      slPairAdd(&pairs,val,label);
      }
  sqlFreeResult(&sr);
  slPairValSortCase(&pairs);
  return pairs;
  }
  
  struct slPair *mdbVarsSearchable(struct sqlConnection *conn, boolean hasTableName, boolean hasFileName)
  // returns a white list of mdb vars that actually exist in the current DB.
  // Searchable vars are only for table or file objects.  Further restrict to vars associated with tableName, fileName or both.
  {
  if (!hasTableName && !hasFileName)
      errAbort("mdbVarsSearchable requests vals associated with neither table nor files.\n");
  
  char *tableName = mdbTableName(conn,TRUE); // Look for sandBox name first
  
  char letter = 'A';
  struct slPair *cvApproved = cvWhiteList(TRUE,FALSE);
  struct slPair *relevant = NULL;
  struct dyString *dyQuery = dyStringNew(256);
  while(cvApproved != NULL)
      {
      struct slPair *oneVar = slPopHead(&cvApproved);
      dyStringClear(dyQuery);
      dyStringPrintf(dyQuery, "select count(DISTINCT %c.val) from %s %c where %c.var = '%s'",letter,tableName,letter,letter,oneVar->name);
  
      mdbSearchableQueryRestictForTablesOrFiles(dyQuery,tableName,letter, hasTableName, hasFileName);
      int count = sqlQuickNum(conn,dyStringContents(dyQuery));
      //warn("%d %s",count,dyStringContents(dyQuery));
  
      if(count > 1)  // If there is only one value then searching on that variable is useless!
          slAddHead(&relevant, oneVar);
      else
          slPairFree(&oneVar);
      }
  dyStringFree(&dyQuery);
  slReverse(&relevant);
  return relevant;
  }