536e32480cf89093ba8b82ff4e73b0a9ca6ac389
chmalee
  Fri Oct 28 13:29:33 2022 -0700
Fix multiTerm searches and plain chromosome searches, refs #29693

diff --git src/hg/lib/hgFind.c src/hg/lib/hgFind.c
index 8b75f1d..1c0ff15 100644
--- src/hg/lib/hgFind.c
+++ src/hg/lib/hgFind.c
@@ -3288,32 +3288,32 @@
     tsrList = trixSearch(category->trix, keyCount, keyWords, tsmExpand);
     struct errCatch *errCatch = errCatchNew();
     if (errCatchStart(errCatch))
         initSnippetIndex(category->trix);
     errCatchEnd(errCatch);
     // silently return if there was a problem opening the snippet index
     if (errCatch->gotError || errCatch->gotWarning)
         return FALSE;
     errCatchFree(&errCatch);
     }
 struct trixSearchResult *tsr = NULL;
 int len = 0;
 struct hgPosTable *table = NULL;
 AllocVar(table);
 table->searchTime = -1;
-table->name = category->name;
-table->description = category->description;
+table->name = cloneString(category->name);
+table->description = cloneString(category->description);
 for (tsr = tsrList; tsr != NULL; tsr = tsr->next)
     {
     if (startsWith(category->name,"publicHubs"))
         {
         // Check that this public hubs result is for our current database
         char *itemId[5];
         int numItems = chopString(cloneString(tsr->itemId), ":", itemId, ArraySize(itemId));
         if (numItems <= 2 || isEmpty(itemId[2]) || !sameString(itemId[2], database))
             continue;
         }
     struct errCatch *errCatch = errCatchNew();
     if (errCatchStart(errCatch))
         {
         addSnippetForResult(tsr, category->trix);
         }
@@ -3346,76 +3346,90 @@
         break;
     }
 
 if (table->posList != NULL)
     {
     slReverse(&table->posList);
     if (measureTiming)
         table->searchTime = clock1000() - startTime;
     slAddHead(&hgp->tableList, table);
     ret = TRUE;
     }
 return ret;
 }
 
 static boolean userDefinedSearch(char *db, char *term, int limitResults, struct cart *cart,
-                            struct hgPositions *hgp, struct searchCategory *categories, boolean measureTiming)
+                            struct hgPositions *hgp, struct searchCategory *categories, boolean multiTerm, boolean measureTiming)
 /* If a search type(s) is specified in the cart, perform that search.
  * If the search is successful, fill in hgp and return TRUE. */
 {
 boolean foundIt = FALSE;
 struct hash *foundSpecHash = hashNew(0);
 struct hgFindSpec *shortList = NULL, *longList = NULL;
 struct trackDb *hubCategoryList = NULL;
 // get all the lists of what to query:
 if (!trackHubDatabase(db))
     {
     if (categories)
         myLoadFindSpecs(db, categories, &shortList, &longList);
     else
         hgFindSpecGetAllSpecs(db, &shortList, &longList);
     }
 // lastly search any included track hubs, or in the case of an assembly hub, any of the tracks
 hubCategoryList = hubCategoriesToTdbList(categories);
 struct hgFindSpec *hfs;
 for (hfs = shortList; hfs != NULL; hfs = hfs->next)
     {
-    boolean foundSpec = hgFindUsingSpec(cart, db, hfs, term, limitResults, hgp, FALSE, 0, 0, FALSE, measureTiming);
+    boolean foundSpec = hgFindUsingSpec(cart, db, hfs, term, limitResults, hgp, FALSE, 0, 0, multiTerm, measureTiming);
     if (foundSpec)
         hashAdd(foundSpecHash, hfs->searchTable, hfs->searchTable);
     foundIt |= foundSpec;
+
+    // for multiTerm searches (like '15q11;15q13'), each individual component
+    // must resolve to a single position, so break once we find the first match
+    if (multiTerm && foundSpec)
+        break;
     }
+if (multiTerm && !foundIt)
+    {
     for (hfs = longList; hfs != NULL; hfs = hfs->next)
         {
         if (hashFindVal(foundSpecHash, hfs->searchTable) != NULL)
             continue;
-    foundIt |= hgFindUsingSpec(cart, db, hfs, term, limitResults, hgp, FALSE, 0, 0, FALSE, measureTiming);
+        foundIt |= hgFindUsingSpec(cart, db, hfs, term, limitResults, hgp, FALSE, 0, 0, multiTerm, measureTiming);
         }
     // lastly search any included track hubs, or in the case of an assembly hub, any of the tracks
     if (hubCategoryList)
         foundIt |= findBigBedPosInTdbList(cart, db, hubCategoryList, term, hgp, NULL, measureTiming);
+    }
+
+// multiTerm searches must resolve to a single range on a chromosome, so don't
+// do these non positional searches if a multiTerm was requested
+if (!multiTerm)
+    {
     getLabelsForHubs();
     struct searchCategory *category;
     for (category = categories; category != NULL; category = category->next)
         {
         if (startsWith("trackDb", category->id)
                 || sameString(category->id, "helpDocs")
                 || sameString(category->id, "publicHubs"))
             {
             foundIt |= doTrixQuery(category, term, hgp, db, measureTiming);
             }
         }
+    }
 
 return foundIt;
 }
 
 static boolean singleSearch(char *db, char *term, int limitResults, struct cart *cart,
                             struct hgPositions *hgp, boolean measureTiming)
 /* If a search type is specified in the CGI line (not cart), perform that search. 
  * If the search is successful, fill in hgp as a single-pos result and return TRUE. */
 {
 char *search = cgiOptionalString("singleSearch");
 if (search == NULL)
     return FALSE;
 
 cartRemove(cart, "singleSearch");
 boolean foundIt = FALSE;
@@ -3645,32 +3659,52 @@
 term = trimSpaces(term);
 if(isEmpty(term))
     return hgp;
 
 hgp->query = cloneString(term);
 hgp->database = db;
 if (extraCgi == NULL)
     extraCgi = "";
 hgp->extraCgi = cloneString(extraCgi);
 
 if (singleSearch(db, term, limitResults, cart, hgp, measureTiming))
     return hgp;
 
 if (categories != NULL)
     {
-    if (!matchesHgvs(cart, db, term, hgp, measureTiming))
-        userDefinedSearch(db, term, limitResults, cart, hgp, categories, measureTiming);
+    char *originalTerm = term;
+    if (hgOfficialChromName(db, term) != NULL) // this mangles the term
+        {
+        char *chrom;
+        int start, end;
+
+        hgParseChromRange(db, term, &chrom, &start, &end);
+        if (relativeFlag)
+            {
+            int chromSize = end;
+            end = start + relEnd;
+            start = start + relStart;
+            if (end > chromSize)
+                end = chromSize;
+            if (start < 0)
+                start = 0;
+            }
+        singlePos(hgp, "Chromosome Range", NULL, "chromInfo", originalTerm,
+              "", chrom, start, end);
+        }
+    else if (!matchesHgvs(cart, db, term, hgp, measureTiming))
+        userDefinedSearch(db, term, limitResults, cart, hgp, categories, multiTerm, measureTiming);
     slReverse(&hgp->tableList);
     if (multiTerm)
         collapseSamePos(hgp);
     fixSinglePos(hgp);
     if (cart && hgp->singlePos && isNotEmpty(hgp->singlePos->highlight))
         cartSetString(cart, "addHighlight", hgp->singlePos->highlight);
     if (hgp->posCount > 0)
         return hgp;
     else
         // if categories was passed in we should explicitly return no results
         // if there weren't any
         return NULL;
     }
 
 /* Allow any search term to end with a :Start-End range -- also support stuff