44ccfacbe3a3d4b300f80d48651c77837a4b571e galt Tue Apr 26 11:12:02 2022 -0700 SQL INJECTION Prevention Version 2 - this improves our methods by making subclauses of SQL that get passed around be both easy and correct to use. The way that was achieved was by getting rid of the obscure and not well used functions sqlSafefFrag and sqlDyStringPrintfFrag and replacing them with the plain versions of those functions, since these are not needed anymore. The new version checks for NOSQLINJ in unquoted %-s which is used to include SQL clauses, and will give an error the NOSQLINJ clause is not present, and this will automatically require the correct behavior by developers. sqlDyStringPrint is a very useful function, however because it was not enforced, users could use various other dyString functions and they operated without any awareness or checking for SQL correct use. Now those dyString functions are prohibited and it will produce an error if you try to use a dyString function on a SQL string, which is simply detected by the presence of the NOSQLINJ prefix. diff --git src/hg/lib/mdb.c src/hg/lib/mdb.c index dcd7f7a..7c52bcc 100644 --- src/hg/lib/mdb.c +++ src/hg/lib/mdb.c @@ -44,35 +44,35 @@ 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. Strings are automatically escaped to allow insertion into the database. */ { -struct dyString *update = newDyString(updateSize); +struct dyString *update = dyStringNew(updateSize); sqlDyStringPrintf(update, "insert into %s set obj='%s', var='%s', val='%s'", tableName, el->obj, el->var, el->val); sqlUpdate(conn, update->string); -freeDyString(&update); +dyStringFree(&update); } 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; } @@ -915,31 +915,31 @@ { 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(64))\n" ")"; if (sqlTableExists(conn,tblName)) verbose(2, "Table '%s' already exists. It will be recreated.\n",tblName); -struct dyString *dy = newDyString(512); +struct dyString *dy = dyStringNew(512); sqlDyStringPrintf(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); @@ -1034,31 +1034,31 @@ 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 { - sqlSafefFrag(query, sizeof(query),"%s where obj = '%s'", tableName, mdbObj->obj); + sqlSafef(query, sizeof(query),"%s where obj = '%s'", tableName, mdbObj->obj); int delCnt = sqlRowCount(conn,query); if (delCnt>0) { sqlSafef(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) @@ -1070,31 +1070,31 @@ { sqlSafef(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 { - sqlSafefFrag(query, sizeof(query),"%s where obj = '%s'", tableName, mdbObj->obj); + sqlSafef(query, sizeof(query),"%s where obj = '%s'", tableName, mdbObj->obj); int delCnt = sqlRowCount(conn,query); if (delCnt>0) { sqlSafef(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) @@ -1179,165 +1179,194 @@ } // ------------------ 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 & 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 exists, else MDB_DEFAULT_NAME else if (!sqlTableExists(conn,table)) return NULL; -struct dyString *dy = newDyString(4096); +struct dyString *dy = dyStringNew(4096); sqlDyStringPrintf(dy, "select obj,var,val from %s", table); if (mdbObj != NULL && mdbObj->obj != NULL) { - sqlDyStringPrintf(dy, " where obj %-s '%s'", - (strchr(mdbObj->obj,'%') ? "like" : "="), mdbObj->obj); + sqlDyStringPrintf(dy, " where obj "); + if (strchr(mdbObj->obj,'%')) + sqlDyStringPrintf(dy, "like"); + else + sqlDyStringPrintf(dy, "="); + sqlDyStringPrintf(dy, " '%s'", mdbObj->obj); struct mdbVar *mdbVar; for (mdbVar=mdbObj->vars;mdbVar!=NULL;mdbVar=mdbVar->next) { if (mdbVar==mdbObj->vars) - dyStringPrintf(dy, " and ("); + sqlDyStringPrintf(dy, " and ("); else - dyStringPrintf(dy, " or "); + sqlDyStringPrintf(dy, " or "); if (mdbVar->var != NULL) { if (mdbVar->val != NULL) - dyStringPrintf(dy, "("); - sqlDyStringPrintf(dy, "var %-s '%s'", - (strchr(mdbVar->var,'%') ? "like" : "="), mdbVar->var); + sqlDyStringPrintf(dy, "("); + sqlDyStringPrintf(dy, "var "); + if (strchr(mdbVar->var,'%')) + sqlDyStringPrintf(dy, "like"); + else + sqlDyStringPrintf(dy, "="); + sqlDyStringPrintf(dy,"'%s'",mdbVar->var); } if (mdbVar->val != NULL) { if (mdbVar->var != NULL) - dyStringPrintf(dy, " and "); - sqlDyStringPrintf(dy, "val %-s '%s'", - (strchr(mdbVar->val,'%') ? "like" : "="), mdbVar->val); + sqlDyStringPrintf(dy, " and "); + sqlDyStringPrintf(dy, "val "); + if (strchr(mdbVar->val,'%')) + sqlDyStringPrintf(dy, "like"); + else + sqlDyStringPrintf(dy, "="); + sqlDyStringPrintf(dy,"'%s'",mdbVar->val); if (mdbVar->var != NULL) - dyStringPrintf(dy, ")"); + sqlDyStringPrintf(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, ")"); + sqlDyStringPrintf(dy, ")"); } verbose(2, "Requesting mdbObjQuery query:\n\t%s;\n",dyStringContents(dy)); -struct mdb *mdb = mdbLoadByQuery(conn, dyStringCannibalize(&dy)); +struct mdb *mdb = mdbLoadByQuery(conn, dyStringContents(dy)); slSort(&mdb,mdbCmp); // Use internal sort instead of ORDER BY because of mysql inefficiency struct mdbObj *mdbObjs = mdbObjsLoadFromMemory(&mdb,buildHash); verbose(3, "Returned %d object(s) with %d var(s).\n", mdbObjCount(mdbObjs,TRUE),mdbObjCount(mdbObjs,FALSE)); +dyStringFree(&dy); 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 exists, else MDB_DEFAULT_NAME else if (!sqlTableExists(conn,table)) return NULL; -struct dyString *dy = newDyString(4096); +struct dyString *dy = dyStringNew(4096); sqlDyStringPrintf(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 "); + sqlDyStringPrintf(dy, " where (var "); else - dyStringPrintf(dy, " OR (var "); + sqlDyStringPrintf(dy, " OR (var "); if (rootVar->notEqual && rootVar->vals == NULL) - dyStringPrintf(dy, "%s", strchr(rootVar->var,'%') ? "NOT " : "!"); - // one of: "NOT LIKE". "!=" or "NOT EXISTS" + { + if (strchr(rootVar->var,'%')) + sqlDyStringPrintf(dy, "NOT"); + else + sqlDyStringPrintf(dy, "!"); + } if (rootVar->vals != NULL && rootVar->vals->val != NULL && strlen(rootVar->vals->val) > 0) { - sqlDyStringPrintf(dy, "%-s '%s'", - (strchr(rootVar->var,'%') ? "like" : "="), rootVar->var); + if (strchr(rootVar->var,'%')) + sqlDyStringPrintf(dy, "like"); + else + sqlDyStringPrintf(dy, "="); + sqlDyStringPrintf(dy, " '%s'", rootVar->var); } else - dyStringPrintf(dy, "EXISTS"); + sqlDyStringPrintf(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 "); + sqlDyStringPrintf(dy, " and val "); if (rootVar->notEqual) - dyStringPrintf(dy, "%s", strchr(limbVal->val,'%') ? "NOT " : "!"); + { + if (strchr(limbVal->val,'%')) + sqlDyStringPrintf(dy, "NOT"); + else + sqlDyStringPrintf(dy, "!"); + } if (limbVal->next == NULL) // only one val { - sqlDyStringPrintf(dy, "%-s '%s'", - (strchr(limbVal->val,'%')?"like":"="), limbVal->val); + if (strchr(limbVal->val,'%')) + sqlDyStringPrintf(dy, "like"); + else + sqlDyStringPrintf(dy, "="); + sqlDyStringPrintf(dy, " '%s'", limbVal->val); break; } else - dyStringPrintf(dy, "in ("); + sqlDyStringPrintf(dy, "in ("); multiVals=TRUE; } else - dyStringPrintf(dy, ","); + sqlDyStringPrintf(dy, ","); sqlDyStringPrintf(dy, "'%s'", limbVal->val); } if (multiVals) - dyStringPrintf(dy, ")"); - dyStringPrintf(dy, ")"); + sqlDyStringPrintf(dy, ")"); + sqlDyStringPrintf(dy, ")"); } verbose(2, "Requesting mdbByVarsQuery query:\n\t%s;\n",dyStringContents(dy)); -struct mdb *mdb = mdbLoadByQuery(conn, dyStringCannibalize(&dy)); +struct mdb *mdb = mdbLoadByQuery(conn, dyStringContents(dy)); verbose(3, "rows (vars) returned: %d\n",slCount(mdb)); slSort(&mdb,mdbVarValCmp); // Use internal sort instead of ORDER BY because of mysql inefficiency 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)); +dyStringFree(&dy); return mdbByVarsFromMem; } struct mdbByVar *mdbByVarQueryByVar(struct sqlConnection *conn,char *table,char *varName,char *val) // Query a single metadata variable & 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; } @@ -1376,107 +1405,131 @@ // => SELECT T1.obj,T1.var,T1.val FROM metaDb T1 // JOIN metaDb T2 WHERE T1.obj = T2.obj // AND T2.var = 'cell' AND T2.val NOT IN ('GM12878','K562')' // ORDER BY T1.obj, T1.var; // "cell=GM% cell!=GM12878" (very powerful) // SELECT T1.obj,T1.var,T1.val FROM metaDb T1 // JOIN metaDb T2 WHERE T1.obj = T2.obj AND T2.var = 'cell' AND T2.val LIKE 'GM%' // JOIN metaDb T3 WHERE T1.obj = T3.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 exists, else MDB_DEFAULT_NAME else if (!sqlTableExists(conn,table)) return NULL; -struct dyString *dy = newDyString(4096); +struct dyString *dy = dyStringNew(4096); sqlDyStringPrintf(dy, "SELECT T1.obj,T1.var,T1.val FROM %s T1", table); struct mdbByVar *rootVar; int tix; for (rootVar=mdbByVars,tix=2;rootVar!=NULL;rootVar=rootVar->next,tix++) { boolean hasVal = (rootVar->vals != NULL); boolean varWild = (strchr(rootVar->var,'%') != NULL); // If you want objects where var='cell' does not exist, then we need the tricky and inscrutable // LEFT JOIN metaDb T2 ON T2.obj = T1.obj AND T2.var = 'cell' WHERE T2.obj is NULL if (!hasVal && rootVar->notEqual) - dyStringAppend(dy, " LEFT"); + sqlDyStringPrintf(dy, " LEFT"); sqlDyStringPrintf(dy, " JOIN %s T%d ON T%d.obj = T1.obj AND T%d.var ",table,tix,tix,tix); // var = 'x' || var != 'x' || var LIKE 'x%' || var NOT LIKE 'x%' if (hasVal && rootVar->notEqual && rootVar->vals == NULL) - dyStringAppend(dy, (varWild ? "NOT " : "!")); - sqlDyStringPrintf(dy, "%-s '%s'",(varWild ? "LIKE" : "="), rootVar->var); + { + if (varWild) + sqlDyStringPrintf(dy, "NOT "); + else + sqlDyStringPrintf(dy, "!"); + } + if (varWild) + sqlDyStringPrintf(dy, "LIKE"); + else + sqlDyStringPrintf(dy, "="); + sqlDyStringPrintf(dy, " '%s'", rootVar->var); // Finish the tricky and inscrutable LEFT JOIN / WHERE NULL if (!hasVal && rootVar->notEqual) - dyStringPrintf(dy, " WHERE T%d.obj IS NULL",tix); + sqlDyStringPrintf(dy, " WHERE T%d.obj IS NULL",tix); // Now 1 or more vals. First some booleans struct mdbLimbVal *limbVal; boolean multiVals = (rootVar->vals != NULL && rootVar->vals->next != NULL); boolean wilds = FALSE; for (limbVal=rootVar->vals;wilds == FALSE && limbVal!=NULL; limbVal=limbVal->next) wilds = (limbVal->val != NULL && strchr(limbVal->val,'%') != NULL); // breaks when true // Now walk through vals creating: // AND (val = 'x' or val = 'y') or val IN ('a','b','c') or val NOT LIKE 'd%' for (limbVal=rootVar->vals;limbVal!=NULL;limbVal=limbVal->next) { if (limbVal->val == NULL || strlen(limbVal->val) < 1) continue; if (limbVal==rootVar->vals) // First val { if (wilds && multiVals) - dyStringAppend(dy, " AND ("); // starts AND (val LIKE 'd%' or ...) + sqlDyStringPrintf(dy, " AND ("); // starts AND (val LIKE 'd%' or ...) else - dyStringAppend(dy, " AND "); // starts AND val = 'a' || AND val IN ('a','b'...) - dyStringPrintf(dy, "T%d.val ",tix); + sqlDyStringPrintf(dy, " AND "); // starts AND val = 'a' || AND val IN ('a','b'...) + sqlDyStringPrintf(dy, "T%d.val ",tix); } else // successive vals { if (wilds && multiVals) - dyStringPrintf(dy, " or T%d.val ",tix); // continues LIKE 'd%' + sqlDyStringPrintf(dy, " or T%d.val ",tix); // continues LIKE 'd%' else - dyStringAppend(dy, ","); // continues IN ('a' + sqlDyStringPrintf(dy, ","); // continues IN ('a' } if (limbVal==rootVar->vals // First val || (wilds && multiVals)) // and successive if wildcards { boolean valWild = (strchr(limbVal->val,'%') != NULL); if (rootVar->notEqual) - dyStringAppend(dy, (valWild || limbVal->next)?"NOT ":"!"); - dyStringAppend(dy, (valWild ? "LIKE " : (!multiVals || wilds ? "= " : "IN ("))); + { + if (valWild || limbVal->next) + sqlDyStringPrintf(dy, "NOT "); + else + sqlDyStringPrintf(dy, "!"); + } + if (valWild) + sqlDyStringPrintf(dy, "LIKE "); + else + { + if (!multiVals || wilds) + sqlDyStringPrintf(dy, "= "); + else + sqlDyStringPrintf(dy, "IN ("); + } + } sqlDyStringPrintf(dy, "'%s'", limbVal->val); } if (multiVals) - dyStringPrintf(dy, ")"); // closes IN ('a','b','c') || AND (val LIKE 'd%' or val LIKE 'e%') + sqlDyStringPrintf(dy, ")"); // closes IN ('a','b','c') || AND (val LIKE 'd%' or val LIKE 'e%') } verbose(2, "Requesting mdbObjsQueryByVars query:\n\t%s;\n",dyStringContents(dy)); -struct mdb *mdb = mdbLoadByQuery(conn, dyStringCannibalize(&dy)); +struct mdb *mdb = mdbLoadByQuery(conn, dyStringContents(dy)); verbose(3, "rows (vars) returned: %d\n",slCount(mdb)); slSort(&mdb,mdbCmp); // Use internal sort instead of ORDER BY because of mysql inefficiency struct mdbObj *mdbObjs = mdbObjsLoadFromMemory(&mdb,TRUE); verbose(3, "Returned %d object(s) with %d var(s).\n", mdbObjCount(mdbObjs,TRUE),mdbObjCount(mdbObjs,FALSE)); +dyStringFree(&dy); 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; @@ -3334,51 +3387,51 @@ 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); sqlDyStringPrintf(dyQuery,"select l1.obj, l1.var, l1.val from %s l1",tableName); if (var != NULL || val != NULL) sqlDyStringPrintf(dyQuery," JOIN %s l2 ON l2.obj = l1.obj and ", tableName); if (var != NULL) sqlDyStringPrintf(dyQuery,"l2.var = '%s'", var); if (var != NULL && val != NULL) - dyStringAppend(dyQuery," and "); + sqlDyStringPrintf(dyQuery," and "); if (val != NULL) { - dyStringAppend(dyQuery,"l2.val "); + sqlDyStringPrintf(dyQuery,"l2.val "); if (sameString(op, "in")) - dyStringPrintf(dyQuery,"in (%s)", val); + sqlDyStringPrintf(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")) sqlDyStringPrintf(dyQuery,"like '%%%s%%'", val); else if (limit > 0 && strlen(val) != limit) sqlDyStringPrintf(dyQuery,"like '%.*s%%'", limit, val); else sqlDyStringPrintf(dyQuery,"= '%s'", val); } verbose(2, "Requesting mdbObjSearch query:\n\t%s;\n",dyStringContents(dyQuery)); -struct mdb *mdb = mdbLoadByQuery(conn, dyStringCannibalize(&dyQuery)); +struct mdb *mdb = mdbLoadByQuery(conn, dyStringContents(dyQuery)); verbose(3, "rows (vars) returned: %d\n",slCount(mdb)); slSort(&mdb,mdbCmp); // Use internal sort instead of ORDER BY because of mysql inefficiency struct mdbObj *mdbObjs = mdbObjsLoadFromMemory(&mdb,TRUE); - +dyStringFree(&dyQuery); 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 continue; // to do the right thing @@ -3491,32 +3544,33 @@ char letter = 'A'; struct dyString *dyQuery = dyStringNew(512); if (limit > 0) sqlDyStringPrintf(dyQuery,"select distinct LEFT(%c.val,%d)",letter,limit); else sqlDyStringPrintf(dyQuery,"select distinct %c.val",letter); sqlDyStringPrintf(dyQuery," from %s %c",tableName,letter); mdbSearchableQueryRestictForTablesOrFiles(dyQuery,tableName, letter, hasTableName, hasFileName); // Need to append 'where' AFTER qualifying joins! sqlDyStringPrintf(dyQuery, " where %c.var='%s'", letter,var); -retVal = sqlQuickList(conn, dyStringCannibalize(&dyQuery)); +retVal = sqlQuickList(conn, dyStringContents(dyQuery)); slNameSortCase(&retVal); +dyStringFree(&dyQuery); 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. { if (!hasTableName && !hasFileName)