054cf0f3a8507c04aecde986ecee4f2979b8d4f3
chmalee
  Mon Nov 10 15:00:32 2025 -0800
Make listExisting endpoint to hubApi/liftOver respect fromGenome and toGenome arguments, taking precedence over the filter argument. If both fromGenome and toGenome arguments are present they are ANDed together

diff --git src/hg/hubApi/liftOver.c src/hg/hubApi/liftOver.c
index 8ce55f0346e..8592d415495 100644
--- src/hg/hubApi/liftOver.c
+++ src/hg/hubApi/liftOver.c
@@ -1,257 +1,269 @@
 /* liftOver functions */
 
 #include "dataApi.h"
 #include "hgFind.h"
 #include "cartTrackDb.h"
 #include "cartJson.h"
 #include "genark.h"
 #include "asmAlias.h"
 #include "assemblyList.h"
 #include "liftOver.h"
 #include "net.h"
 
 /**** SHOULD BE IN LIBRARY - code from hgConvert.c ******/
 static long chainTotalBlockSize(struct chain *chain)
 /* Return sum of sizes of all blocks in chain */
 {
 struct cBlock *block;
 long total = 0;
 for (block = chain->blockList; block != NULL; block = block->next)
     total += block->tEnd - block->tStart;
 return total;
 }
 /**** SHOULD BE IN LIBRARY - code from hgConvert.c ******/
 
 static void chainListOut(char *fromDb, char* toDb, int origSize, char *fromPos, struct chain *chainList)
 /* given the list of chains, output the list in JSON */
 {
 struct chain *chain = NULL;
 char position[4096];
 char coverage[4096];
 struct jsonWrite *jw = apiStartOutput();
 jsonWriteListStart(jw, "from");
 jsonWriteListStart(jw, NULL);
 jsonWriteString(jw, NULL, fromDb);
 jsonWriteString(jw, NULL, fromPos);
 jsonWriteListEnd(jw);
 jsonWriteListEnd(jw);
 jsonWriteListStart(jw, "to");
 for (chain = chainList; chain != NULL; chain = chain->next)
     {
     int blockSize;
     int qStart, qEnd;
     if (chain->qStrand == '-')
         {
         qStart = chain->qSize - chain->qEnd;
         qEnd = chain->qSize - chain->qStart;
         }
     else
         {
         qStart = chain->qStart;
         qEnd = chain->qEnd;
         }
     blockSize = chainTotalBlockSize(chain);
     safef(position, sizeof(position), "%s:%d-%d", chain->qName, qStart+1, qEnd);
     safef(coverage, sizeof(coverage), "%3.1f%% of bases, %3.1f%% of span",
         100.0 * blockSize / origSize,
         100.0 * (chain->tEnd - chain->tStart) / origSize);
     jsonWriteListStart(jw, NULL);
     jsonWriteString(jw, NULL, toDb);
     jsonWriteString(jw, NULL, position);
     jsonWriteString(jw, NULL, coverage);
     jsonWriteListEnd(jw);
     }
 jsonWriteListEnd(jw);
 apiFinishOutput(0, NULL, jw);
 }
 
 static void listExisting()
 /* output the fromDb,toDb from liftOverChain.hgcentral SQL table */
 {
 long long itemCount = 0;
 char *filter = cgiOptionalString(argFilter);
+char *fromDb = cgiOptionalString(argFromGenome);
+char *toDb = cgiOptionalString(argToGenome);
 
 struct sqlConnection *conn = hConnectCentral();
 char *tableName = cloneString(liftOverChainTable());
-char query[1024];
-sqlSafef(query, sizeof(query), "SELECT count(*) FROM %s", tableName);
-long long totalRows = sqlQuickLongLong(conn, query);
+struct dyString *query = newDyString(0);
+sqlDyStringPrintf(query, "SELECT count(*) FROM %s", tableName);
+long long totalRows = sqlQuickLongLong(conn, dyStringContents(query));
+dyStringClear(query);
 
-if (isEmpty(filter))
+if (isNotEmpty(fromDb) || isNotEmpty(toDb))
     {
-    sqlSafef(query, sizeof(query), "SELECT fromDb,toDb FROM %s LIMIT %d", tableName, maxItemsOutput);
+    sqlDyStringPrintf(query, "SELECT fromDb,toDb FROM %s WHERE ", tableName);
+    if (isNotEmpty(fromDb))
+        sqlDyStringPrintf(query, "LOWER(fromDb) = LOWER('%s') %s ", fromDb, isNotEmpty(toDb) ? "AND" : "");
+    if (isNotEmpty(toDb))
+        sqlDyStringPrintf(query, "LOWER(toDb) = LOWER('%s') ", toDb);
+    sqlDyStringPrintf(query, "LIMIT %d;", maxItemsOutput);
+    }
+else if (isNotEmpty(filter))
+    {
+    sqlDyStringPrintf(query, "SELECT fromDb,toDb FROM %s WHERE LOWER(fromDb) = LOWER('%s') OR LOWER(toDb) = LOWER('%s') LIMIT %d;", tableName, filter, filter, maxItemsOutput);
     }
 else
     {
-    sqlSafef(query, sizeof(query), "SELECT fromDb,toDb FROM %s WHERE LOWER(fromDb) = LOWER('%s') OR LOWER(toDb) = LOWER('%s') LIMIT %d;", tableName, filter, filter, maxItemsOutput);
+    sqlDyStringPrintf(query, "SELECT fromDb,toDb FROM %s LIMIT %d", tableName, maxItemsOutput);
     }
 
 char *dataTime = sqlTableUpdate(conn, tableName);
 time_t dataTimeStamp = sqlDateToUnixTime(dataTime);
 replaceChar(dataTime, ' ', 'T');	/* ISO 8601 */
 struct jsonWrite *jw = apiStartOutput();
 jsonWriteString(jw, "dataTime", dataTime);
 jsonWriteNumber(jw, "dataTimeStamp", (long long)dataTimeStamp);
 
 jsonWriteListStart(jw, "existingLiftOvers");
-struct sqlResult *sr = sqlGetResult(conn, query);
+struct sqlResult *sr = sqlGetResult(conn, dyStringCannibalize(&query));
 char **row;
 while ((row = sqlNextRow(sr)) != NULL)
     {
     ++itemCount;
     jsonWriteListStart(jw, NULL);
     jsonWriteString(jw, NULL, row[0]);
     jsonWriteString(jw, NULL, row[1]);
     jsonWriteListEnd(jw);
     }
 jsonWriteListEnd(jw);
 jsonWriteNumber(jw, "totalLiftOvers", totalRows);
 jsonWriteNumber(jw, "itemsReturned", itemCount);
 
 apiFinishOutput(0, NULL, jw);
 hDisconnectCentral(&conn);
 }
 
 /**** SHOULD BE IN LIBRARY - code from hgConvert.c ******/
 static char *skipWord(char *fw)
 /* skips over current word to start of next. 
  * Error for this not to exist. */
 {
 char *s;
 s = skipToSpaces(fw);
 if (s == NULL)
     errAbort("Expecting two words in .ra file line %s\n", fw);
 s = skipLeadingSpaces(s);
 if (s == NULL)
     errAbort("Expecting two words in .ra file line %s\n", fw);
 return s;
 }
 
 static struct chain *chainLoadIntersecting(char *fileName,
 	char *chrom, int start, int end)
 /* Load the chains that intersect given region. */
 {
 struct lineFile *lf = netLineFileOpen(fileName);
 char *line;
 int chromNameSize = strlen(chrom);
 struct chain *chainList = NULL, *chain;
 #ifdef SOON	/* Put in if we index. */
 boolean gotChrom = FALSE;
 #endif  /* SOON */
 int chainCount = 0;
 
 while (lineFileNextReal(lf, &line))
     {
     if (startsWith("chain", line) && isspace(line[5]))
         {
 	++chainCount;
 	line = skipWord(line);	/* Skip over 'chain' */
 	line = skipWord(line);	/* Skip over chain score */
 	if (startsWith(chrom, line) && isspace(line[chromNameSize]))
 	    {
 #ifdef SOON	/* Put in if we index. */
 	    gotChrom = TRUE;
 #endif  /* SOON */
 	    lineFileReuse(lf);
 	    chain = chainReadChainLine(lf);
 	    if (rangeIntersection(chain->tStart, chain->tEnd, start, end) > 0)
 		{
 		chainReadBlocks(lf, chain);
 		slAddHead(&chainList, chain);
 		}
 	    else
 	        chainFree(&chain);
 	    }
 #ifdef SOON	/* Put in if we index. */
 	else if (gotChrom)
 	    break;	/* We assume file is sorted by chromosome, so we're done. */
 #endif /* SOON */
 	}
     }
 lineFileClose(&lf);
 slReverse(&chainList);
 return chainList;
 }
 
 static struct chain *chainLoadAndTrimIntersecting(char *fileName,
 	char *chrom, int start, int end)
 /* Load the chains that intersect given region, and trim them
  * to fit region. */
 {
 struct chain *rawList, *chainList = NULL, *chain, *next;
 rawList = chainLoadIntersecting(fileName, chrom, start, end);
 for (chain = rawList; chain != NULL; chain = next)
     {
     struct chain *subChain, *chainToFree;
     next = chain->next;
     chainSubsetOnT(chain, start, end, &subChain, &chainToFree);
     if (subChain != NULL)
 	slAddHead(&chainList, subChain);
     if (chainToFree != NULL)
         chainFree(&chain);
     }
 slSort(&chainList, chainCmpScore);
 return chainList;
 }
 /**** SHOULD BE IN LIBRARY - code from hgConvert.c ******/
 
 void apiLiftOver(char *words[MAX_PATH_INFO])
 /* 'liftOver' function words[1] is the subCommand */
 {
 char *extraArgs = verifyLegalArgs(argLiftOver);
 if (extraArgs)
     apiErrAbort(err400, err400Msg, "extraneous arguments found for function /liftOver'%s'", extraArgs);
 
 if (sameWordOk("listExisting", words[1]))
     {
     listExisting();
     return;
     }
 
 char *fromGenome = cgiOptionalString(argFromGenome);
 char *toGenome = cgiOptionalString(argToGenome);
 char *chrom = cgiOptionalString(argChrom);
 char *start = cgiOptionalString(argStart);
 char *end = cgiOptionalString(argEnd);
 
 if (isEmpty(fromGenome) || isEmpty(toGenome) || isEmpty(chrom) || isEmpty(start) || isEmpty(end))
     apiErrAbort(err400, err400Msg, "must have all arguments: %s, %s, %s, %s, %s  for endpoint '/liftOver", argFromGenome, argToGenome, argChrom, argStart, argEnd);
 
 unsigned uStart = 0;
 unsigned uEnd = 0;
 uStart = sqlUnsigned(start);
 uEnd = sqlUnsigned(end);
 if (uEnd < uStart)
     apiErrAbort(err400, err400Msg, "given start coordinate %u is greater than given end coordinate", uStart, uEnd);
 
 struct dbDb *fromDb = hDbDb(fromGenome);
 if (fromDb == NULL)
     {
     fromDb = genarkLiftOverDb(fromGenome);
     }
 if (fromDb == NULL)
     apiErrAbort(err400, err400Msg, "can not find 'fromGenome=%s' for endpoint '/liftOver", fromGenome);
 struct dbDb *toDb = hDbDb(toGenome);
 if (toDb == NULL)
     {
     toDb = genarkLiftOverDb(toGenome);
     }
 if (toDb == NULL)
     apiErrAbort(err400, err400Msg, "can not find 'toGenome=%s' for endpoint '/liftOver", toGenome);
 
 char *fileName = liftOverChainFile(fromDb->name, toDb->name);
 if (isEmpty(fileName))
     apiErrAbort(err400, err400Msg, "Unable to find a chain file from %s to %s - please contact support", fromGenome, toGenome);
 fileName = hReplaceGbdbMustDownload(fileName);
 char fromPos[4096];
 safef(fromPos, sizeof(fromPos), "%s:%u-%u", chrom, uStart, uEnd);
 char *nChrom;
 int nStart, nEnd;
 if (!hgParseChromRange(NULL, fromPos, &nChrom, &nStart, &nEnd))
     apiErrAbort(err400, err400Msg, "position %s is not in chrom:start-end format", fromPos);
 int origSize = nEnd - nStart;
 struct chain *chainList = chainLoadAndTrimIntersecting(fileName, nChrom, nStart, nEnd);
 if (chainList == NULL)
     apiErrAbort(err400, err400Msg, "Sorry, this position %s is not found in the %s assembly", fromPos, toGenome);
 chainListOut(fromGenome, toGenome, origSize, fromPos, chainList);
 }