4450e227ad04fd0176b28b6dffddabc0352a953f
chmalee
  Mon Apr 13 14:28:42 2026 -0700
Restore xref term in search result name/description for findSpec
searches with an xrefTable.

Commit 5197ebd63b5 batched the per-xref doQuery into one combined
"or <idField> in (...)" query for performance, but dropped the
id -> xrefTerm mapping: pos->name became the raw id and pos->description
was no longer set for xref matches.

Rebuild the mapping as a hash from xrefList and use it when processing
each row to restore pos->name = xrefTerm and pos->description = "(id)".
The two-query shape is preserved. refs #37345

diff --git src/hg/lib/hgFind.c src/hg/lib/hgFind.c
index 5772125d374..9e56efbe735 100644
--- src/hg/lib/hgFind.c
+++ src/hg/lib/hgFind.c
@@ -2537,30 +2537,50 @@
 else
     safef(buf, sizeof(buf), "%s", hfs->searchTable);
 description = cloneString(buf);
 
 if (hgp->tableList != NULL &&
     sameString(hgp->tableList->name, hfs->searchTable) &&
     sameString(hgp->tableList->description, description))
     table = hgp->tableList;
 
 // this is taken from hgFindSpecCustom.c:checkQueryFormat() and changed to
 // capture the name field for a table
 static char *queryFormatRegexForIdField =
     "^select [[:alnum:]]+, ?[[:alnum:]]+, ?[[:alnum:]]+, ?[[:alnum:]]+ "
     "from %s where ([[:alnum:]]+) (r?like|=) ['\"]?.*%s.*['\"]?$";
 regmatch_t substrs[3];
+
+// Build an id -> xrefTerm lookup so result rows can be labeled with the
+// human-readable xref term (e.g. gene symbol) instead of the raw id.
+struct hash *xrefHash = NULL;
+if (xrefList)
+    {
+    struct slPair *p;
+    for (p = xrefList; p != NULL; p = p->next)
+        {
+        if (isNotEmpty(p->name) && isNotEmpty((char *)p->val))
+            {
+            if (xrefHash == NULL)
+                xrefHash = hashNew(0);
+            // first xref term wins if multiple xrefs map to the same id
+            if (!hashLookup(xrefHash, (char *)p->val))
+                hashAdd(xrefHash, (char *)p->val, p->name);
+            }
+        }
+    }
+
 for (tPtr = tableList;  tPtr != NULL;  tPtr = tPtr->next )
     {
     struct dyString *query = sqlDyStringCreate(hfs->query, tPtr->name, term);
     if (xrefList)
         {
         // NOTE: hfsPolish guarantees the query format and allows the below regex to work.
         // See hgFindSpecCustom.c for more details, specifically checkQueryFormat()
         if (regexMatchSubstr(hfs->query, queryFormatRegexForIdField, substrs, ArraySize(substrs)))
             {
             char *idField = regexSubstringClone(hfs->query, substrs[1]);
             sqlDyStringPrintf(query, " or %s in (", idField);
             struct slName *vals = NULL;
             struct slPair *ptr = xrefList;
             for (; ptr != NULL; ptr = ptr->next)
                 {
@@ -2586,60 +2606,73 @@
     while ((row = sqlNextRow(sr)) != NULL)
         {
         if(table == NULL)
             {
             AllocVar(table);
             table->searchTime = -1;
             table->description = description;
             table->name = cloneString(hfs->searchTable);
             slAddHead(&hgp->tableList, table);
             }
         found = TRUE;
         AllocVar(pos);
         pos->chrom = cloneString(row[0]);
         pos->chromStart = atoi(row[1]);
         pos->chromEnd = atoi(row[2]);
+        char *xrefTerm = xrefHash ? hashFindVal(xrefHash, row[3]) : NULL;
+        if (isNotEmpty(xrefTerm))
+            {
+            truncatef(buf, sizeof(buf), "%s", xrefTerm);
+            pos->name = cloneString(buf);
+            safef(buf, sizeof(buf), "(%s%s)",
+                  termPrefix ? termPrefix : "", row[3]);
+            pos->description = cloneString(buf);
+            }
+        else
+            {
             safef(buf, sizeof(buf), "%s%s",
                   termPrefix ? termPrefix : "", row[3]);
             pos->name = cloneString(buf);
+            }
         pos->browserName = cloneString(row[3]);
         if (relativeFlag && (pos->chromStart + relEnd) <= pos->chromEnd)
             {
             pos->chromEnd   = pos->chromStart + relEnd;
             pos->chromStart = pos->chromStart + relStart;
             }
         else if (padding > 0 && !multiTerm)
             {
             // highlight the item bases to distinguish from padding
             pos->highlight = addHighlight(db, pos->chrom, pos->chromStart, pos->chromEnd);
             int chromSize = hChromSize(db, pos->chrom);
             pos->chromStart -= padding;
             pos->chromEnd   += padding;
             if (pos->chromStart < 0)
                 pos->chromStart = 0;
             if (pos->chromEnd > chromSize)
                 pos->chromEnd = chromSize;
             }
         slAddHead(&table->posList, pos);
         }
 
     }
 if (table != NULL)
     slReverse(&table->posList);
 sqlFreeResult(&sr);
 hFreeConn(&conn);
 slFreeList(&tableList);
+hashFree(&xrefHash);
 if (measureTiming && table)
     table->searchTime += clock1000() - startTime;
 return(found);
 }
 
 static boolean hgFindUsingSpec(struct cart *cart,
                         char *db, struct hgFindSpec *hfs, char *term, int limitResults,
 			struct hgPositions *hgp, boolean relativeFlag,
 			int relStart, int relEnd, boolean multiTerm, boolean measureTiming)
 /* Perform the search described by hfs on term.  If successful, put results
  * in hgp and return TRUE.  (If not, don't modify hgp.) */
 {
 struct slPair *xrefList = NULL; 
 boolean found = FALSE;