src/hg/lib/metaTbl.c 1.1

1.1 2010/03/18 01:48:26 tdreszer
Finally making progress on a metadata table.
Index: src/hg/lib/metaTbl.c
===================================================================
RCS file: src/hg/lib/metaTbl.c
diff -N src/hg/lib/metaTbl.c
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ src/hg/lib/metaTbl.c	18 Mar 2010 01:48:26 -0000	1.1
@@ -0,0 +1,767 @@
+/* metaTbl.c was originally generated by the autoSql program, which also
+ * generated metaTbl.h and metaTbl.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 "metaTbl.h"
+
+static char const rcsid[] = "$Id$";
+
+void metaTblStaticLoad(char **row, struct metaTbl *ret)
+/* Load a row from metaTbl table into ret.  The contents of ret will
+ * be replaced at the next call to this function. */
+{
+
+ret->objName = row[0];
+ret->objType = row[1];
+ret->var = row[2];
+ret->varType = row[3];
+ret->val = row[4];
+}
+
+struct metaTbl *metaTblLoadByQuery(struct sqlConnection *conn, char *query)
+/* Load all metaTbl 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 metaTblFreeList(). */
+{
+struct metaTbl *list = NULL, *el;
+struct sqlResult *sr;
+char **row;
+
+sr = sqlGetResult(conn, query);
+while ((row = sqlNextRow(sr)) != NULL)
+    {
+    el = metaTblLoad(row);
+    slAddHead(&list, el);
+    }
+slReverse(&list);
+sqlFreeResult(&sr);
+return list;
+}
+
+void metaTblSaveToDb(struct sqlConnection *conn, struct metaTbl *el, char *tableName, int updateSize)
+/* Save metaTbl 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 metaTblSaveToDbEscaped() */
+{
+struct dyString *update = newDyString(updateSize);
+dyStringPrintf(update, "insert into %s values ( '%s','%s','%s','%s',%s)",
+	tableName,  el->objName,  el->objType,  el->var,  el->varType,  el->val);
+sqlUpdate(conn, update->string);
+freeDyString(&update);
+}
+
+void metaTblSaveToDbEscaped(struct sqlConnection *conn, struct metaTbl *el, char *tableName, int updateSize)
+/* Save metaTbl 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 metaTblSaveToDb().
+ * For example automatically copies and converts:
+ * "autosql's features include" --> "autosql\'s features include"
+ * before inserting into database. */
+{
+struct dyString *update = newDyString(updateSize);
+char  *objName, *objType, *var, *varType, *val;
+objName = sqlEscapeString(el->objName);
+objType = sqlEscapeString(el->objType);
+var = sqlEscapeString(el->var);
+varType = sqlEscapeString(el->varType);
+val = sqlEscapeString(el->val);
+
+dyStringPrintf(update, "insert into %s values ( '%s','%s','%s','%s','%s')",
+	tableName,  objName,  objType,  var,  varType,  val);
+sqlUpdate(conn, update->string);
+freeDyString(&update);
+freez(&objName);
+freez(&objType);
+freez(&var);
+freez(&varType);
+freez(&val);
+}
+
+struct metaTbl *metaTblLoad(char **row)
+/* Load a metaTbl from row fetched with select * from metaTbl
+ * from database.  Dispose of this with metaTblFree(). */
+{
+struct metaTbl *ret;
+
+AllocVar(ret);
+ret->objName = cloneString(row[0]);
+ret->objType = cloneString(row[1]);
+ret->var = cloneString(row[2]);
+ret->varType = cloneString(row[3]);
+ret->val = cloneString(row[4]);
+return ret;
+}
+
+struct metaTbl *metaTblLoadAll(char *fileName)
+/* Load all metaTbl from a whitespace-separated file.
+ * Dispose of this with metaTblFreeList(). */
+{
+struct metaTbl *list = NULL, *el;
+struct lineFile *lf = lineFileOpen(fileName, TRUE);
+char *row[5];
+
+while (lineFileRow(lf, row))
+    {
+    el = metaTblLoad(row);
+    slAddHead(&list, el);
+    }
+lineFileClose(&lf);
+slReverse(&list);
+return list;
+}
+
+struct metaTbl *metaTblLoadAllByChar(char *fileName, char chopper)
+/* Load all metaTbl from a chopper separated file.
+ * Dispose of this with metaTblFreeList(). */
+{
+struct metaTbl *list = NULL, *el;
+struct lineFile *lf = lineFileOpen(fileName, TRUE);
+char *row[5];
+
+while (lineFileNextCharRow(lf, chopper, row, ArraySize(row)))
+    {
+    el = metaTblLoad(row);
+    slAddHead(&list, el);
+    }
+lineFileClose(&lf);
+slReverse(&list);
+return list;
+}
+
+struct metaTbl *metaTblCommaIn(char **pS, struct metaTbl *ret)
+/* Create a metaTbl out of a comma separated string.
+ * This will fill in ret if non-null, otherwise will
+ * return a new metaTbl */
+{
+char *s = *pS;
+
+if (ret == NULL)
+    AllocVar(ret);
+ret->objName = sqlStringComma(&s);
+ret->objType = sqlStringComma(&s);
+ret->var = sqlStringComma(&s);
+ret->varType = sqlStringComma(&s);
+ret->val = sqlStringComma(&s);
+*pS = s;
+return ret;
+}
+
+void metaTblFree(struct metaTbl **pEl)
+/* Free a single dynamically allocated metaTbl such as created
+ * with metaTblLoad(). */
+{
+struct metaTbl *el;
+
+if ((el = *pEl) == NULL) return;
+freeMem(el->objName);
+freeMem(el->objType);
+freeMem(el->var);
+freeMem(el->varType);
+freeMem(el->val);
+freez(pEl);
+}
+
+void metaTblFreeList(struct metaTbl **pList)
+/* Free a list of dynamically allocated metaTbl's */
+{
+struct metaTbl *el, *next;
+
+for (el = *pList; el != NULL; el = next)
+    {
+    next = el->next;
+    metaTblFree(&el);
+    }
+*pList = NULL;
+}
+
+void metaTblOutput(struct metaTbl *el, FILE *f, char sep, char lastSep)
+/* Print out metaTbl.  Separate fields with sep. Follow last field with lastSep. */
+{
+if (sep == ',') fputc('"',f);
+fprintf(f, "%s", el->objName);
+if (sep == ',') fputc('"',f);
+fputc(sep,f);
+if (sep == ',') fputc('"',f);
+fprintf(f, "%s", el->objType);
+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->varType);
+if (sep == ',') fputc('"',f);
+fputc(sep,f);
+if (sep == ',') fputc('"',f);
+fprintf(f, "%s", el->val);
+if (sep == ',') fputc('"',f);
+fputc(lastSep,f);
+}
+
+/* -------------------------------- End autoSql Generated Code -------------------------------- */
+
+#define METATBL_DEFAULT_NAME "metaTbl"
+
+
+enum metaObjType metaObjTypeStringToEnum(char *objType)
+// Convert metadata objType string to enum
+{
+if(sameWord(objType,"table"))
+    return otTable;
+if(sameWord(objType,"file"))
+    return otFile;
+return otUnknown;
+}
+
+char *metaObjTypeEnumToString(enum metaObjType objType)
+// Convert metadata objType enum string
+{
+switch (objType)
+    {
+    case otTable:  return "table";
+    case otFile:   return "file";
+    default:       return "unknown";
+    }
+}
+
+enum metaVarType metaVarTypeStringToEnum(char *varType)
+// Convert metadata varType string to enum
+{
+if(sameWord(varType,"txt"))
+    return vtTxt;
+if(sameWord(varType,"binary"))
+    return vtBinary;
+return vtUnknown;
+}
+
+char *metaVarTypeEnumToString(enum metaVarType varType)
+// Convert metadata varType enum string
+{
+switch (varType)
+    {
+    case vtTxt:    return "txt";
+    case vtBinary: return "binary";
+    default:       return "unknown";
+    }
+}
+
+struct metaObj *metadataLineParse(char *line)
+/* Parses a single formatted metadata line. */
+{
+char *words[256]; // A lot!
+int thisWord = 0;
+struct metaObj *metaObj;
+struct metaVar *metaVar;
+char *cloneLine = cloneString(line);
+
+    // initial chop and determine if this looks like metadata
+    int count = chopByWhiteRespectDoubleQuotes(cloneLine,words,ArraySize(words));
+    if(count < 3 || words[thisWord] == NULL || differentWord(words[thisWord],"metadata"))
+        {
+        errAbort("This is not formatted metadata:\n\t%s\n",line);
+        }
+
+    verbose(3, "metadataLineParse() word count:%d\n\t%s\n",count,line);
+    // Get objName and figure out if this is a delete line
+    AllocVar(metaObj);
+    thisWord++;
+    if(strchr(words[thisWord], '=') == NULL)
+        {
+        metaObj->objName = cloneString(words[thisWord++]);
+        verbose(3, "metadataLineParse() objName=%s\n",metaObj->objName);
+        if(sameWord(words[thisWord],"delete"))
+            {
+            metaObj->deleteThis = TRUE;
+            thisWord++;
+            }
+            if(thisWord < count && strchr(words[thisWord], '=') == NULL)
+                {
+                metaObj->objType = metaObjTypeStringToEnum(words[thisWord]);
+                if(metaObj->objType == otUnknown)
+                    {
+                    errAbort("Unknown objType '%s'.  This is not properly formatted metadata:\n\t%s\n",words[thisWord],line);
+                    //metaObjsFree(&metaObj);
+                    //return NULL;
+                    }
+                thisWord++;
+                }
+        if(thisWord < count && sameWord(words[thisWord],"delete"))  // Could be objName delete... or objName objType delete
+            {
+            metaObj->deleteThis = TRUE;
+            thisWord++;
+            }
+        }
+
+    // From now on all words are expected to be var=val pairs!
+    metaObj->varHash = hashNew(0);
+    for(;thisWord<count;thisWord++)
+        {
+        if(strchr(words[thisWord], '=') == NULL)
+            {
+            errAbort("Expected 'var=val' but found '%s'.  This is not properly formatted metadata:\n\t%s\n",words[thisWord],line);
+            //metaObjsFree(&metaObj);
+            //return NULL;
+            }
+        AllocVar(metaVar);
+        metaVar->var = cloneNextWordByDelimiter(&(words[thisWord]),'=');
+        metaVar->varType = vtTxt;                               // FIXME: binary?
+        metaVar->val = cloneString(words[thisWord]);
+        verbose(3, "metadataLineParse() var=val: %s=%s\n",metaVar->var,metaVar->val);
+        struct metaVar *oldVar = (struct metaVar *)hashFindVal(metaObj->varHash, metaVar->var);
+        if(oldVar)
+            {
+            verbose(1, "The same variable appears twice: %s=%s and %s=%s.  Ignoring second value.\n",
+                oldVar->var,oldVar->val,metaVar->var,metaVar->val);
+            freeMem(metaVar->var);
+            freeMem(metaVar->val);
+            freeMem(metaVar);
+            }
+        else
+            {
+            hashAdd(metaObj->varHash, metaVar->var, metaVar); // pointer to struct to resolve type
+            slAddHead(&(metaObj->vars),metaVar);
+            }
+        }
+
+    // Special for old style ENCODE metadata
+    if(metaObj->objName == NULL)
+        {
+        char * tableName = NULL;
+        char * fileName = NULL;
+        for(metaVar  = metaObj->vars;
+            metaVar != NULL && (tableName == NULL || fileName == NULL);
+            metaVar  = metaVar->next)
+            {
+            if(sameString(metaVar->var,"tableName"))
+                tableName = metaVar->val;
+            else if(sameString(metaVar->var,"fileName"))
+                fileName = metaVar->val;
+            }
+        if(tableName != NULL)
+            {
+            verbose(2, "tableName:%s\n",tableName);
+            if(fileName == NULL || startsWithWordByDelimiter(tableName,'.',fileName))
+                {
+                metaObj->objName = cloneString(tableName);
+                metaObj->objType = otTable;
+                }
+            }
+        else if(fileName != NULL)
+            {
+            verbose(2, "fileName:%s\n",fileName);
+            // NOTE: that the file object is the root of the name, so that file.fastq.gz = file.fastq
+            metaObj->objName = cloneFirstWordByDelimiter(fileName,'.');
+            metaObj->objType = otFile;
+            }
+        }
+
+if(metaObj->objName == NULL)
+    {
+    errAbort("No objName found. This is not properly formatted metadata:\n\t%s\n",line);
+    //metaObjsFree(&metaObj);
+    //return NULL;
+    }
+if(metaObj->objType == otUnknown) // NOTE: defaulting to table
+    {
+    metaObj->objType = otTable;
+    }
+    slReverse(&(metaObj->vars));
+    verbose(2, "metadataLineParse() objName=%s(%s) %s(%s)=%s\n",
+    metaObj->objName, metaObjTypeEnumToString(metaObj->objType),metaObj->vars->var,metaVarTypeEnumToString(metaObj->vars->varType),metaObj->vars->val);
+return metaObj;
+}
+
+struct metaObj *metaObjsLoadFromFormattedFile(char *fileName)
+// Load all metaObjs from a file containing metadata formatted lines
+{
+struct metaObj *metaObjs = NULL;
+struct lineFile *lf = lineFileOpen(fileName, TRUE);
+char *line;
+
+while (lineFileNext(lf, &line,NULL))
+    {
+    struct metaObj *metaObj = metadataLineParse(line);
+    if(metaObj == NULL)
+        {
+        metaObjsFree(&metaObjs);
+        return NULL;
+        }
+    slAddHead(&metaObjs,metaObj);
+    }
+    lineFileClose(&lf);
+    slReverse(&metaObjs);  // Go ahead and keep this in file order
+    return metaObjs;
+}
+
+static struct metaObj *metaObjsLoadFromMemory(struct metaTbl **metaTblPtr,boolean buildHashes)
+// Load all metaObjs from in memory metaTbl struct, cannibalize strings.  Expects sorted order.
+{
+struct metaObj *metaObj  = NULL;
+struct metaObj *metaObjs = NULL;
+struct metaVar *metaVar;
+struct metaTbl *thisRow;
+while((thisRow = slPopHead(metaTblPtr)) != NULL)
+    {
+    if (metaObj == NULL || differentString(thisRow->objName,metaObj->objName) )
+        {
+        // Finish last object before starting next!
+        if(metaObj!= NULL)
+            slReverse(&(metaObjs->vars));
+        // Start new object
+        AllocVar(metaObj);
+        metaObj->objName = thisRow->objName;
+        metaObj->objType = metaObjTypeStringToEnum(thisRow->objType);
+        freeMem(thisRow->objType);
+        if ( buildHashes )
+            metaObj->varHash = hashNew(0);
+        slAddHead(&metaObjs,metaObj);
+        }
+    else
+        {
+        freeMem(thisRow->objName);  // Already got this from prev row
+        freeMem(thisRow->objType);
+        }
+    AllocVar(metaVar);
+    metaVar->var     = thisRow->var;
+    metaVar->varType = metaVarTypeStringToEnum(thisRow->varType);
+    metaVar->val     = thisRow->val;
+    slAddHead(&(metaObj->vars),metaVar);
+    if ( buildHashes )
+        hashAddUnique(metaObj->varHash, metaVar->var, metaVar); // pointer to struct to resolve type
+
+    freeMem(thisRow);
+    }
+
+slReverse(&metaObjs);
+
+return metaObjs;
+}
+
+static struct metaRootVar *metaRootVarsLoadFromMemory(struct metaTbl **metaTblPtr,boolean buildHashes)
+// Load all metaVars from in memorys metaTbl struct, cannibalize strings.  Expects sorted order.
+{
+struct metaRootVar *rootVars = NULL;
+struct metaRootVar *rootVar  = NULL;
+struct metaLimbVal *limbVal  = NULL;
+struct metaLeafObj *leafObj;
+struct metaTbl *thisRow;
+while((thisRow = slPopHead(metaTblPtr)) != NULL)
+    {
+    // Start at root
+    if (rootVar == NULL || differentString(thisRow->var,rootVar->var) )
+        {
+        // Finish last var before starting next!
+        if(rootVar != NULL)
+            slReverse(&(rootVar->vals));
+        // Start new var
+        AllocVar(rootVar);
+        limbVal = NULL;  // Very important!
+        rootVar->var     = thisRow->var;
+        rootVar->varType = metaVarTypeStringToEnum(thisRow->varType);
+        freeMem(thisRow->varType);
+        if ( buildHashes )
+            rootVar->valHash = hashNew(0);
+        slAddHead(&rootVars,rootVar);
+        }
+    else
+        {
+        freeMem(thisRow->var);  // Already got this from prev row
+        freeMem(thisRow->varType);
+        }
+
+    // Continue with limb
+    if (limbVal == NULL || differentString(thisRow->val,limbVal->val) )
+        {
+        // Finish last val before starting next!
+        if(limbVal != NULL)
+            slReverse(&(limbVal->objs));
+        // Start new val
+        AllocVar(limbVal);
+        limbVal->val     = thisRow->val;     // FIXME: binary?
+        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->objName = thisRow->objName;
+    leafObj->objType = metaObjTypeStringToEnum(thisRow->objType);
+    freeMem(thisRow->objType);
+    if ( buildHashes )
+        hashAddUnique(limbVal->objHash, leafObj->objName, leafObj); // Pointer to struct to resolve type!
+    slAddHead(&(limbVal->objs),leafObj);
+
+    freeMem(thisRow);
+    }
+
+slReverse(&rootVars);
+
+return rootVars;
+}
+
+int metaObjsSetToDb(struct sqlConnection *conn,char *tableName,struct metaObj *metaObjs,boolean replace)
+// Adds or updates metadata obj/var pairs into the named table.  Returns total rows affected
+{
+char query[8192];
+struct metaObj *metaObj;
+struct metaVar *metaVar;
+int count = 0;
+
+    if(tableName == NULL)
+        tableName = METATBL_DEFAULT_NAME;
+
+for(metaObj = metaObjs;metaObj != NULL; metaObj = metaObj->next)
+    {
+    // Handle delete requests first
+    if(metaObj->deleteThis)
+        {
+        if(metaObj->vars == NULL) // deletes all
+            {
+            safef(query, sizeof(query),"%s where objName = '%s'",tableName,metaObj->objName);
+            int delCnt = sqlRowCount(conn,query);
+
+            if(delCnt>0)
+                {
+                safef(query, sizeof(query),
+                    "delete from %s where objName = '%s'",tableName,metaObj->objName);
+                verbose(2, "Delete of %d rows requested: %s\n",delCnt, query);
+                sqlUpdate(conn, query);
+                count += delCnt;
+                }
+            }
+        else  // deletes selected vars
+            {
+            for(metaVar = metaObj->vars;metaVar != NULL; metaVar = metaVar->next)
+                {
+                safef(query, sizeof(query),
+                    "select objName from %s where objName = '%s' and var = '%s'",
+                    tableName,metaObj->objName,metaVar->var);
+                if(sqlExists(conn,query))
+                    {
+                    safef(query, sizeof(query),
+                        "delete from %s where objName = '%s' and var = '%s'",
+                        tableName,metaObj->objName,metaVar->var);
+                    verbose(2, "Requested: %s\n",query);
+                    sqlUpdate(conn, query);
+                    count++;
+                    }
+                }
+            }
+        continue;  // Done with this metaObj
+        }
+    else if (replace)  // If replace then clear out deadwood before inserting new vars
+        {
+        safef(query, sizeof(query),"%s where objName = '%s'",tableName,metaObj->objName);
+        int delCnt = sqlRowCount(conn,query);
+
+        if(delCnt>0)
+            {
+            safef(query, sizeof(query),
+                "delete from %s where objName = '%s'",tableName,metaObj->objName);
+            verbose(2, "Replacing %d rows requested: %s\n",delCnt, query);
+            sqlUpdate(conn, query);
+            count += delCnt;
+            }
+        }
+
+    // Now it is time for update or add!
+    for(metaVar = metaObj->vars;metaVar != NULL; metaVar = metaVar->next)
+        {
+        // Be sure to check for var existence first, then update
+        if (!replace)
+            {
+            struct metaObj *objExists = metaObjVarLoadFromTbl(conn,tableName,metaObj->objName,metaVar->var);
+            if(objExists)
+                {
+                if(differentString(metaVar->val,objExists->vars->val)
+                || metaVar->varType != objExists->vars->varType)
+                    {
+                    safef(query, sizeof(query),
+                        "update %s set varType = '%s', val = '%s' where objName = '%s' and var = '%s'",
+                            tableName,
+                            metaVarTypeEnumToString(metaVar->varType),sqlEscapeString(metaVar->val), // FIXME: binary val?
+                            metaObj->objName,metaVar->var);
+                    verbose(2, "Requested: %s\n",query);
+                    sqlUpdate(conn, query);
+                    count++;
+                    }
+                metaObjsFree(&objExists);
+                continue;  // The object was found/updated so done with it
+                }
+            }
+        // Finally ready to insert new vars
+        safef(query, sizeof(query),
+            "insert into %s values ( '%s','%s','%s','%s','%s')",
+                tableName,metaObj->objName,metaObjTypeEnumToString(metaObj->objType),
+                          metaVar->var,    metaVarTypeEnumToString(metaVar->varType),
+                          sqlEscapeString(metaVar->val)); // FIXME: binary val?
+        verbose(2, "Requested: %s\n",query);
+        sqlUpdate(conn, query);
+        count++;
+        }
+    }
+return count;
+}
+
+struct metaObj *metaObjsLoadAllFromTbl(struct sqlConnection *conn,char *tableName)
+// Load all metaObjs from a table (default metaTbl).  Will build varHash.
+{
+    char query[256];
+    if(tableName == NULL)
+        tableName = METATBL_DEFAULT_NAME;
+    safef(query, sizeof(query),
+        "select objName,objType,var,varType,val from %s order by objName, var",
+        tableName);
+    struct metaTbl *metaTbl = metaTblLoadByQuery(conn, query);
+    return metaObjsLoadFromMemory(&metaTbl,TRUE);
+}
+
+struct metaObj *metaObjLoadFromTbl(struct sqlConnection *conn,char *tableName,char *objName)
+// Load a metaObj from a table (default metaTbl).  Will build varHash.
+{
+    char query[256];
+    if(tableName == NULL)
+        tableName = METATBL_DEFAULT_NAME;
+    safef(query, sizeof(query),
+        "select objName,objType,var,varType,val from %s where objName = '%s' order by var",
+        tableName,objName);
+    struct metaTbl *metaTbl = metaTblLoadByQuery(conn, query);
+    return metaObjsLoadFromMemory(&metaTbl,TRUE);
+}
+
+struct metaObj *metaObjVarLoadFromTbl(struct sqlConnection *conn,char *tableName,char *objName,char *varName)
+// Load a single metadata obj/var pair from a table (default metaTbl).  No objHash build
+{
+    char query[256];
+    if(tableName == NULL)
+        tableName = METATBL_DEFAULT_NAME;
+    safef(query, sizeof(query),
+        "select objName,objType,var,varType,val from %s where objName = '%s' and var = '%s'",
+        tableName,objName,varName);
+    struct metaTbl *metaTbl = metaTblLoadByQuery(conn, query);
+    return metaObjsLoadFromMemory(&metaTbl,FALSE);  // No need for has with one var!
+}
+
+struct metaRootVar *metaRootVarsLoadAllFromTbl(struct sqlConnection *conn,char *tableName)
+// Load all metaVars from a table (default metaTbl) for searching var->val->obj.  Will build objHash.
+{
+    char query[256];
+    if(tableName == NULL)
+        tableName = METATBL_DEFAULT_NAME;
+    safef(query, sizeof(query),
+        "select objName,objType,var,varType,val from %s order by var, val, objName",
+        tableName);
+    struct metaTbl *metaTbl = metaTblLoadByQuery(conn, query);
+    return metaRootVarsLoadFromMemory(&metaTbl,TRUE);
+}
+
+struct metaRootVar *metaRootVarLoadFromTbl(struct sqlConnection *conn,char *tableName,char *varName)
+// Load a metaVar from a table (default metaTbl) for searching val->obj.  Will build objHash
+{
+    char query[256];
+    if(tableName == NULL)
+        tableName = METATBL_DEFAULT_NAME;
+    safef(query, sizeof(query),
+        "select objName,objType,var,val from %s where var = '%s' order by val, objName",
+        tableName,varName);
+    struct metaTbl *metaTbl = metaTblLoadByQuery(conn, query);
+    return metaRootVarsLoadFromMemory(&metaTbl,TRUE);
+}
+
+void metaObjsFree(struct metaObj **metaObjsPtr)
+// Frees one or more metadata objects and any contained metaVars.  Will free any hashes as well.
+{
+
+if(metaObjsPtr != NULL && *metaObjsPtr != NULL)
+    {
+    // free all roots
+    struct metaObj *metaObj = NULL;
+    while((metaObj = slPopHead(metaObjsPtr)) != NULL)
+        {
+        // Free hash first (shared memory)
+        if(metaObj->varHash != NULL)
+            hashFree(&(metaObj->varHash));
+
+        // free all leaves
+        struct metaVar *metaVar = NULL;
+        while((metaVar = slPopHead(&(metaObj->vars))) != NULL)
+            {
+            if(metaVar->val)
+                freeMem(metaVar->val);
+            if(metaVar->var)
+                freeMem(metaVar->var);
+            freeMem(metaVar);
+            }
+        // The rest of root
+        if(metaObj->objName)
+            freeMem(metaObj->objName);
+        freeMem(metaObj);
+        }
+    freez(metaObjsPtr);
+    }
+}
+
+void metaRootVarsFree(struct metaRootVar **metaRootVarsPtr)
+// Frees one or more metadata vars and any contained vals and objs.  Will free any hashes as well.
+{
+if(metaRootVarsPtr != NULL && *metaRootVarsPtr != NULL)
+    {
+    // free all roots
+    struct metaRootVar *rootVar = NULL;
+    while((rootVar = slPopHead(metaRootVarsPtr)) != NULL)
+        {
+        // Free hash first (shared memory)
+        if(rootVar->valHash != NULL)
+            hashFree(&(rootVar->valHash));
+
+        // free all limbs
+        struct metaLimbVal *limbVal = NULL;
+        while((limbVal = slPopHead(&(rootVar->vals))) != NULL)
+            {
+            // Free hash first (shared memory)
+            if(limbVal->objHash != NULL)
+                hashFree(&(limbVal->objHash));
+
+            // free all leaves
+            struct metaLeafObj *leafObj = NULL;
+            while((leafObj = slPopHead(&(limbVal->objs))) != NULL)
+                {
+                if(leafObj->objName != NULL)
+                    freeMem(leafObj->objName);
+                freeMem(leafObj);
+                }
+            // The rest of limb
+            if(limbVal->val)
+                freeMem(limbVal->val);
+            freeMem(limbVal);
+            }
+        // The rest of root
+        if(rootVar->var)
+            freeMem(rootVar->var);
+        freeMem(rootVar);
+        }
+    freez(metaRootVarsPtr);
+    }
+}
+
+// TODO: Print formatted metadata lines!
+
+